Agile App Co.

Stripe and ApplePay with Xamarin Forms

How to add ApplePay to your Xamarin Forms app using Stripe.

Since there was no where available on the internet that shared how to integrate ApplePay with Stripe and Xamarin Forms we decided to share the information as to how we did it. 

Firstly you need the usual which you have probably got already, a Stripe Account, Apple Developer Account, and Visual Studio. You don't actually need a back end to take payments using Stripe and Apple Pay on Xamarin Forms. Most of the detail below is the code after you have set up the necessary requirements. There are quite a few articels explaining how to get to the bit where you need to start writing code but not much out there after that. 

 

Summary

  • Go on to Stripe and configure ApplePay in settings. You will need the CSR that you generate on there to create your certificate in the Apple Developer portal More info here
  • Head to https://developer.apple.com to get Apple talking to Stripe which means setting up a merchant id in Apple Developer and enabling Apple Pay Payment Processing for your app.
  • Get ApplePay configured in your app. 

 

Get ApplePay configured in your app. 

Add the Stripe.ios Nuget in to your ios project and initialise it in the AppDelegate

var c = new Stripe.iOS.ApiClient(App.StripePublishableKey);
            c.Configuration.AppleMerchantIdentifier = App.StripeMerchantId;
            Stripe.iOS.ApiClient.SharedClient.PublishableKey = App.StripePublishableKey;
            

 

Next, create some kind of renderer to add an ApplePay button to your screen. We just used a button renderer but you could just use a view renderer. Alot of the below code can be refactored but the key thing to look at is the bit where a delegate is assigned. We'll look at that next. 

public class PaymentButtonRenderer : ButtonRenderer
    {
        public PaymentButton _button { get; set; }
        protected override void OnElementChanged(ElementChangedEventArgs<Button> e)
        {
            base.OnElementChanged(e);
            
            if (PKPaymentAuthorizationViewController.CanMakePaymentsUsingNetworks(supportedNetworks))
            {
                var button = new PKPaymentButton(PKPaymentButtonType.Plain, PKPaymentButtonStyle.Black);
                button.TouchUpInside += ApplyPayButtonClicked;

                //button.Center = NativeView.Center;

                //button.AutoresizingMask = UIViewAutoresizing.FlexibleLeftMargin | UIViewAutoresizing.FlexibleRightMargin;

                NativeView.AddSubviews(button);

                _button = Element as PaymentButton;
                if(_button !=null)
                    _button.Clicked += ApplyPayButtonClicked;
            }
            else
            {
                //var lbl = new UILabel();
                //lbl.Text = "Please set up Apple Pay on your device to continue";
                //NativeView.AddSubviews(lbl);
                ShowAuthorizationAlert();
            }

        }
        readonly NSString[] supportedNetworks = {
            PKPaymentNetwork.Amex,
            PKPaymentNetwork.Discover,
            PKPaymentNetwork.MasterCard,
            PKPaymentNetwork.Visa
        };

        private void ApplyPayButtonClicked(object sender, EventArgs e)
        {
            ApplyPayButtonClicked();
        }
        public void ApplyPayButtonClicked()
        {
            if (!PKPaymentAuthorizationViewController.CanMakePaymentsUsingNetworks(supportedNetworks))
            {
                ShowAuthorizationAlert();
                return;
            }

            // Set up our payment request.
            var paymentRequest = new PKPaymentRequest();

            // Our merchant identifier needs to match what we previously set up in
            // the Capabilities window (or the developer portal).
            paymentRequest.MerchantIdentifier = "merchant.co.yourmerchant";
            
            // Both country code and currency code are standard ISO formats. Country
            // should be the region you will process the payment in. Currency should
            // be the currency you would like to charge in.
            paymentRequest.CountryCode = "GB";
            paymentRequest.CurrencyCode = "GBP";

            // The networks we are able to accept.
            paymentRequest.SupportedNetworks = supportedNetworks;

            // Ask your payment processor what settings are right for your app. In
            // most cases you will want to leave this set to ThreeDS.
            paymentRequest.MerchantCapabilities = PKMerchantCapability.ThreeDS;

            // An array of `PKPaymentSummaryItems` that we'd like to display on the
            // sheet (see the MakeSummaryItems method).
            paymentRequest.PaymentSummaryItems = MakeSummaryItems(false);

            // Request shipping information, in this case just postal address.
            paymentRequest.RequiredShippingAddressFields = PKAddressField.PostalAddress;

            // Display the view controller.
            var viewController = new PKPaymentAuthorizationViewController(paymentRequest);
            viewController.Delegate = new PaymentAuthorizationDelegate(_button);

            Window.RootViewController.PresentViewController(viewController, true, null);
            
            //PresentViewController(viewController, true, null);

        }
        
        void ShowAuthorizationAlert()
        {
            try
            {
                var alert = UIAlertController.Create("Error", "This device cannot make payments.", UIAlertControllerStyle.Alert);
                var action = UIAlertAction.Create("Okay", UIAlertActionStyle.Default, null);
                alert.AddAction(action);

                Window.RootViewController.PresentViewController(alert, true, null);
                //PresentViewController(alert, true, null);

            }
            catch (Exception)
            {

                //throw;
            }

        }
        PKPaymentSummaryItem[] MakeSummaryItems(bool requiresInternationalSurcharge)
        {
            var items = new List<PKPaymentSummaryItem>();

            // Product items have a label (a string) and an amount (NSDecimalNumber).
            // NSDecimalNumber is a Cocoa class that can express floating point numbers
            // in Base 10, which ensures precision. It can be initialized with a
            // double, or in this case, a string.

            var productSummaryItem = PKPaymentSummaryItem.Create("Sub-total", new NSDecimalNumber(_button.Amount / 100.00));
            items.Add(productSummaryItem);

            var totalSummaryItem = PKPaymentSummaryItem.Create(App.Merchant, productSummaryItem.Amount);
            // Apply an international surcharge, if needed.
            if (requiresInternationalSurcharge)
            {
                var handlingSummaryItem = PKPaymentSummaryItem.Create("International Handling", new NSDecimalNumber("9.99"));

                // Note how NSDecimalNumber has its own arithmetic methods.
                totalSummaryItem.Amount = productSummaryItem.Amount.Add(handlingSummaryItem.Amount);
                items.Add(handlingSummaryItem);
            }
            items.Add(totalSummaryItem);

            return items.ToArray();
        }
    }

Creating the ApplePay Delegate. You'll notice some references to some commands that are being executed which are dependency property's we added to our custom button. We'll talk about that in the next step. There is a lot of code below that needs refactoring but this is where we had got to when ApplePay was working. 

public class PaymentAuthorizationDelegate : NSObject, IPKPaymentAuthorizationViewControllerDelegate

    {
        public PaymentAuthorizationDelegate(object Data)
        {
            XamButton = Data as PaymentButton;
        }

        public PaymentButton XamButton { get; private set; }

        public IntPtr Handle => throw new NotImplementedException();

        //public IntPtr Handle => throw new NotImplementedException();
        
        public void DidAuthorizePayment(PKPaymentAuthorizationViewController controller, PKPayment payment, Action<PKPaymentAuthorizationStatus> completion)
        {
            Stripe.iOS.ApiClient.SharedClient.CreateToken(payment, tokenComplete);

            controller.DismissViewController(false, DismissComplete);
           

        }

        private void tokenComplete(Stripe.iOS.Token arg0, NSError arg1)
        {
            if(arg0 != null)   
                XamButton.PaymentDidAuthorizeCommand.Execute(arg0.TokenId);
            else
            {
                XamButton.PaymentFinishedCommand.Execute("Payment failed");
            }
        }

        public void Dispose()
        {
            throw new NotImplementedException();
        }

      

        public void PaymentAuthorizationViewControllerDidFinish(PKPaymentAuthorizationViewController controller)
        {

            controller.DismissViewController(true, null);
            XamButton.PaymentFinishedCommand.Execute("Payment was cancelled by user");
        }

        public void WillAuthorizePayment(PKPaymentAuthorizationViewController controller)
        {
            

            XamButton.PaymentWillAuthorizeCommand.Execute(false);

            

        }
        JObject ConvertToJObject(NSData data)
        {
            var jObject = new JObject();
            var json = data.ToString(NSStringEncoding.UTF8);

            if (!string.IsNullOrWhiteSpace(json))
            {
                jObject = JObject.Parse(json.ToString());
            }

            return jObject;
        }
        async Task PerformApplePayAction(PKPayment payment, Action<PKPaymentAuthorizationStatus> completion, PKPaymentAuthorizationViewController controller)
        {
            //var jObject = ConvertToJObject(payment.Token.PaymentData);
            //var paymentServiceModel = new PKPaymentViewModel
            //{
            //    Amount = _applePayModel.ItemsTotalAmount(),
            //    ConsumerReference = _applePayModel.ConsumerRef,
            //    JudoID = _judo.JudoId,
            //    PaymentData = jObject,
            //    PaymentInstrumentName = payment.Token.PaymentInstrumentName ?? string.Empty,
            //    PaymentNetwork = payment.Token.PaymentNetwork ?? string.Empty
            //};

            //var response = await _applePayRequest.Perform(paymentServiceModel);

            //if (response.ConsideredSuccessful)
            //{
            //    if (response.ReceiptModelPresent() && _successCallBack != null)
            //    {
            //        completion(PKPaymentAuthorizationStatus.Success);
            //        controller.DismissViewController(true, null);
            //        _successCallBack(response.ReceiptModel);
            //    }
            //}
            //else
            //{
            //    completion(PKPaymentAuthorizationStatus.Failure);
            //    controller.DismissViewController(true, null);

            //    if (response.ReceiptModelPresent())
            //    {
            //        _failureCallback(response.ErrorModel, response.ReceiptModel);
            //    }
            //    else
            //    {
            //        _failureCallback(response.ErrorModel);
            //    }
            //}
        }
        private void DismissComplete()
        {
           
        }
    }

 

Custom Button / View - We added the below custom button with dependency property's that we could use to trigger events in the ApplePay process. 

public class PaymentButton : Button

    {
        public static readonly BindableProperty PaymentFinishedCommandProperty = BindableProperty.Create(nameof(PaymentFinishedCommand), typeof(Command), typeof(PaymentButton), null);
        public Command PaymentFinishedCommand
        {
            get { return (Command)GetValue(PaymentFinishedCommandProperty); }
            set { SetValue(PaymentFinishedCommandProperty, value); }
        }

        public static readonly BindableProperty PaymentWillAuthorizeCommandProperty = BindableProperty.Create(nameof(PaymentWillAuthorizeCommand), typeof(Command), typeof(PaymentButton), null);
        public Command PaymentWillAuthorizeCommand
        {
            get { return (Command)GetValue(PaymentWillAuthorizeCommandProperty); }
            set { SetValue(PaymentWillAuthorizeCommandProperty, value); }
        }

        public static readonly BindableProperty PaymentDidAuthorizeCommandProperty = BindableProperty.Create(nameof(PaymentDidAuthorizeCommand), typeof(Command), typeof(PaymentButton), null);
        public Command PaymentDidAuthorizeCommand
        {
            get { return (Command)GetValue(PaymentDidAuthorizeCommandProperty); }
            set { SetValue(PaymentDidAuthorizeCommandProperty, value); }
        }

        public static readonly BindableProperty AmountProperty = BindableProperty.Create(nameof(Amount), typeof(long), typeof(PaymentButton), (long)0);
        public long Amount
        {
            get { return (long)GetValue(AmountProperty); }
            set { SetValue(AmountProperty, value); }
        }

 

 

Once a successful response has come back from Apple you can use Stripe.net in your PCL / Standard project to make the payment using reference to the token that ApplePay have provided you. There must have been some dialog between ApplePay and Stripe before this point as Stripe will be aware of the token you are going to send them. 

 

var myCharge = new ChargeCreateOptions()
                    {

                        Amount = BasketTotal,
                        Currency = "gbp",
                        Description = "My ApplePay App",
                        Capture = true,
                        ReceiptEmail = "admin@yoursite.co",
                        SourceId = token as string
                    };

                    var chargeService = new ChargeService();

                    var ret = await chargeService.CreateAsync(myCharge);
                    if (ret.Captured ?? false || ret.Outcome.Type == "authorized")
                    {
    }