iOS Code Snippet [B4X] [B4XPages] STRIPE Checkout - Credit card and Apple Pay

Attached a project which demonstrates how to accept a STRIPE Payment within your app. It is based on the information on this page: https://stripe.com/docs/checkout/integration-builder

On iOS it also allows the use of APPLE PAY.

Unfortunately, Google Pay does not seem to work for me, not sure why. If anyone can work that out, please let me know.

Please forgive the naming convention, it originally was a test for APPLE PAY, but then through the power of B4XPAGES, I realized it will work with ANDROID with only minor tweaks.

It uses a Webview to display the a Standard STRIPE Checkout screen.

PREREQUISITES:
  1. You need you own stripe keys: https://Stripe.com
  2. replace the lines with your own keys
    Stripe Keys:
    #if StoreRelease
    StripeAPIKey = "pk_LIVE_XXXX"
    StripeOtherKey = "sk_LIVE_XXXX"
    
    #else
    StripeAPIKey = "pk_test_XXXX"
    StripeOtherKey = "sk_test_XXXX"
    #end if
        #End If
  3. If you are using the B4A code you need to put the keys in the manifest file.
  4. For iOS you will need to change the Certificate and Provisionfiles

In a real app, you will need to setup your products, the doSession() function has a product hardcoded.

You will also need to handle the Success and Cancel results.

B4X:
Sub wvApple_OverrideUrl (Url As String) As Boolean
    Log($"Override ? (${Url})"$)
    If (Url.Contains("cancel.html")) Then
        wvApple.Visible = False
        xui.MsgboxAsync("Cancelling. Do something ","Hi")
        'handle cancelling here
    else if (Url.contains("success.html")) Then
        wvApple.Visible = False
        xui.MsgboxAsync("Success! Do something ","Hi")
        'Handle success here
    End If  
    Return False
End Sub

The StripeClass is a subset of a much larger class which will handle credit card payments without a webview. Please message me if you would like to know more.


If you use this code, please consider donating,
 

Attachments

  • Project.zip
    13.4 KB · Views: 397

omarruben

Active Member
Licensed User
Longtime User
Question:

After loading and the payment page is shown, there is a small "back arrow", looks like it is coming from stripe
how to erase it or not allow to show?
 

Andrew (Digitwell)

Well-Known Member
Licensed User
Longtime User
That is correct. This is a cancel button. Normally your logo appears next to it. If you press it you should get the cancel URL being called.

There is no way to remove it.
 

james_sgp

Active Member
Licensed User
Longtime User
Hi, I`m trying your app...I copy and pasted my Publishable and Secret keys into the app. But I get an error when I try to compile:

B4X:
ResponseError. Reason: , Response: {
  "error": {
    "message": "Invalid API Key provided: null",
    "type": "invalid_request_error"
  }
}
return failure - {success=false, responsecode=401, data={error={
  "error": {
    "message": "Invalid API Key provided: null",
    "type": "invalid_request_error"
  }
}
}}

Can you advise on the problem?

James
 

Andrew (Digitwell)

Well-Known Member
Licensed User
Longtime User
Hi James,
That's very odd. I just downloaded the sample again tried it on Android and it works fine.

Are you using B4A or B4i?

If you are using B4a then the keys must be placed in the manifest.

I tried using invalid keys in the manifest and got the following error.

B4X:
initialising stripe
Call B4XPages.GetManager.LogEvents = True to enable logging B4XPages events.
** Activity (main) Resume **
*** Service (httputils2service) Create ***
** Service (httputils2service) Start **
ResponseError. Reason: , Response: {
  "error": {
    "message": "Invalid API Key provided: sk_test_**********************mCqq",
    "type": "invalid_request_error"
  }
}
return failure - {success=false, responsecode=401, data={error={
  "error": {
    "message": "Invalid API Key provided: sk_test_**********************mCqq",
    "type": "invalid_request_error"
  }
}
}}

Your keys seem to be Null.

Can you change the log line in StripeClass->initialise to

B4X:
    Log($"initialising stripe ${apikey} ${otherkey}"$)
 

james_sgp

Active Member
Licensed User
Longtime User
I got the class working well in one of my B4A apps, but was wondering can the class make a payment link. Meaning I can send someone an email with a link for payment or put a link in a PDF document for payment?
 

Andrew (Digitwell)

Well-Known Member
Licensed User
Longtime User
Not as it stands as the sessionHTML is created dynamically at run time.
You could save this to a server as a unique file and then send that link.

If you want a payment link, I would do all the work on the server from the start.
Stripe provide plenty of support for this scenario. See

https://stripe.com/docs/payments/checkout

and

I'm a php person, so I would create a php script that would accept the product information from the app and return a url which is the built checkout page.
 

Andrew (Digitwell)

Well-Known Member
Licensed User
Longtime User
Hi James,
Sorry for the late reply, I'm away at the moment...

a couple of questions,

  • is this B4A or B4i?
  • The string that you mentioned above does not appear to end with eith success or failure .html which it should if things were working correctly. Can you provide the complete string?
  • Can you provide the complete error message from the log when the crash happens.
Cheers
 

james_sgp

Active Member
Licensed User
Longtime User
Andrew,
Thanks for the reply, I have replaced my code with a fresh version and is working now, not sure what went wrong.

James
 

yiankos1

Well-Known Member
Licensed User
Longtime User
Great alternate way of payment. Thank you Andrew. If anyone want to use it, just be sure to read this first (in order not to be rejected):

Using Stripe and Apple Pay versus in-app purchases​

Apple Pay doesn’t replace Apple’s In-App Purchase API. You can use any of Stripe’s supported payment methods and Apple Pay in your iOS app to sell physical goods (for example, groceries and clothing) or for services your business provides (for example, club memberships and hotel reservations). These payments are processed through Stripe and you only need to pay Stripe’s processing fee.

Apple’s developer terms require their In-App Purchase API be used for digital “content, functionality, or services,” such as premium content for your app or subscriptions for digital content. Payments made using the In-App Purchase API are processed by Apple and subject to their transaction fees.
 

james_sgp

Active Member
Licensed User
Longtime User
Hi,

I have noticed that if I cancel the payment and they try a second time...the page is blank...which is due to line 5 below:

B4X:
Sub wvApple_OverrideUrl (Url As String) As Boolean
    Log($"Override ? (${Url})"$)
    If (Url.Contains("cancel.html")) Then
        wvApple.Visible = False
        xui.MsgboxAsync("Oh dear...  Your your payment" & CRLF & "Failed or was Cancelled","Ocean Citizen Payment")
        B4XPages.ClosePage(B4XPages.GetPage("payment"))
    else if (Url.contains("success.html")) Then
        wvApple.Visible = False
        xui.MsgboxAsync("Your payment was Successful" & CRLF & CRLF & "Thank You","Ocean Citizen Payment")
        pay_complete
    End If
    Return False
End Sub

I can`t seem to get it to make this webview visible when a second payment is processed, I tried adding 'Visible = true' in several different places but doesn`t seem to do anything?

James
 

Andrew (Digitwell)

Well-Known Member
Licensed User
Longtime User
Hi James,
I don't get this problem. Is it possible that you could upload a small sample app demonstrating the problem.
 

MarcoRome

Expert
Licensed User
Longtime User
Hi @Andrew (Digitwell)

I tried your code on both android and ios and it works great. I think for stripe it is the best solution
Compliments.
I made my donation:
Number Transaction: 3PE99635X78054404

I have a few questions for you:
1. The css style ( https://stripe.digitwellsolutions.com/style.css ) do you have a link where you can type other models ?
Or should this be used?

2. Do APIKeys for Android need to be placed in the manifest? It would be more convenient to insert them in the code (in case you want to change using codes from other customers)

Thank you again 🙏
 

Andrew (Digitwell)

Well-Known Member
Licensed User
Longtime User
Hi @MarcoRome,
Thanks for the donation.
I have a few questions for you:
1. The css style ( https://stripe.digitwellsolutions.com/style.css ) do you have a link where you can type other models ?
Or should this be used?
No you can use any styling your like. I have just checked and that file does not exist. !?

See this link: https://stripe.com/docs/js/including and here for some tips: https://stackoverflow.com/questions/57394130/stripe-checkout-button-style


2. Do APIKeys for Android need to be placed in the manifest? It would be more convenient to insert them in the code (in case you want to change using codes from other customers)

Thank you again 🙏
No you don't have to put them here. It was a simplified attempt to make the STRIPE API key more difficult to find in the code.
It may be better to store on the server and request when required. Also remeber to restrict the Key to only the APIs you are using.
 

MarcoRome

Expert
Licensed User
Longtime User
Hi @Andrew (Digitwell)
If the user closes the app before the transaction returns the result, there is a function to check if that transaction was successful
I tried reading some documentation but couldn't find it
Thank you
Nice day
 

MarcoRome

Expert
Licensed User
Longtime User
Hi @Andrew (Digitwell)
If the user closes the app before the transaction returns the result, there is a function to check if that transaction was successful
I tried reading some documentation but couldn't find it
Thank you
Nice day
Ok Found and works great.

B4X:
Private Sub CheckPayment(id As String)


    Dim basicAuth As String = StripeOtherKey
    Dim su As StringUtils
    Dim authInfo = su.EncodeBase64(basicAuth.GetBytes("UTF8"))
      
    '***** 2. Restituisce id da Elaborare per effettuare il pagamento
    Dim jobid As HttpJob
    'Send a POST request
    jobid.Initialize("", Me)
    jobid.Download($"https://api.stripe.com/v1/checkout/sessions/${id}"$)
    jobid.GetRequest.SetHeader("Authorization", $"Basic ${authInfo}"$)           
    Wait For (jobid) jobDone(jobid As HttpJob)
    If jobid.Success Then
        Log("********************************")
        'Log(jobid.GetString)
        Dim parser As JSONParser
        parser.Initialize(jobid.GetString)
        Dim Root1 As Map = parser.NextObject
        Dim payment_method_collection As String = Root1.Get("payment_method_collection")
        Dim metadata As Map = Root1.Get("metadata")
        Dim after_expiration As String = Root1.Get("after_expiration")
        Dim livemode As String = Root1.Get("livemode")
        Dim amount_total As Int = Root1.Get("amount_total")
        Dim shipping_details As String = Root1.Get("shipping_details")
        Dim subscription As String = Root1.Get("subscription")
        Dim locale As String = Root1.Get("locale")
        Dim payment_link As String = Root1.Get("payment_link")
        Dim mode As String = Root1.Get("mode")
        Dim customer_details As Map = Root1.Get("customer_details")
        Dim address As Map = customer_details.Get("address")
        Dim country As String = address.Get("country")
        Dim city As String = address.Get("city")
        Dim state As String = address.Get("state")
        Dim postal_code As String = address.Get("postal_code")
        Dim line2 As String = address.Get("line2")
        Dim line1 As String = address.Get("line1")
        Dim tax_ids As List = customer_details.Get("tax_ids")
        Dim phone As String = customer_details.Get("phone")
        Dim tax_exempt As String = customer_details.Get("tax_exempt")
        Dim name As String = customer_details.Get("name")
        Dim email As String = customer_details.Get("email")
        Dim consent_collection As String = Root1.Get("consent_collection")
        Dim expires_at As Int = Root1.Get("expires_at")
        Dim allow_promotion_codes As String = Root1.Get("allow_promotion_codes")
        Dim customer_creation As String = Root1.Get("customer_creation")
        Dim currency_conversion As String = Root1.Get("currency_conversion")
        Dim phone_number_collection As Map = Root1.Get("phone_number_collection")
        Dim enabled As String = phone_number_collection.Get("enabled")
        Dim client_reference_id As String = Root1.Get("client_reference_id")
        Dim currency As String = Root1.Get("currency")
        Dim id As String = Root1.Get("id")
        Dim payment_method_options As Map = Root1.Get("payment_method_options")
        Dim billing_address_collection As String = Root1.Get("billing_address_collection")
        Dim success_url As String = Root1.Get("success_url")
        Dim setup_intent As String = Root1.Get("setup_intent")
        Dim invoice_creation As Map = Root1.Get("invoice_creation")
        Dim invoice_data As Map = invoice_creation.Get("invoice_data")
        Dim metadata As Map = invoice_data.Get("metadata")
        Dim account_tax_ids As String = invoice_data.Get("account_tax_ids")
        Dim footer As String = invoice_data.Get("footer")
        Dim custom_fields As String = invoice_data.Get("custom_fields")
        Dim rendering_options As String = invoice_data.Get("rendering_options")
        Dim description As String = invoice_data.Get("description")
        Dim enabled As String = invoice_creation.Get("enabled")
        Dim shipping_address_collection As String = Root1.Get("shipping_address_collection")
        Dim shipping_cost As String = Root1.Get("shipping_cost")
        Dim created As Int = Root1.Get("created")
        Dim custom_fields1 As List = Root1.Get("custom_fields")
        Dim payment_method_types As List = Root1.Get("payment_method_types")
        For Each colpayment_method_types As String In payment_method_types
        Next
        Dim total_details As Map = Root1.Get("total_details")
        Dim amount_tax As Int = total_details.Get("amount_tax")
        Dim amount_discount As Int = total_details.Get("amount_discount")
        Dim amount_shipping As Int = total_details.Get("amount_shipping")
        Dim payment_status As String = Root1.Get("payment_status")
        Dim shipping_options As List = Root1.Get("shipping_options")
        Dim consent As String = Root1.Get("consent")
        Dim url As String = Root1.Get("url")
        Dim recovered_from As String = Root1.Get("recovered_from")
        Dim submit_type As String = Root1.Get("submit_type")
        Dim automatic_tax As Map = Root1.Get("automatic_tax")
        Dim enabled As String = automatic_tax.Get("enabled")
        Dim status As String = automatic_tax.Get("status")
        Dim custom_text As Map = Root1.Get("custom_text")
        Dim submit As String = custom_text.Get("submit")
        Dim shipping_address As String = custom_text.Get("shipping_address")
        Dim customer_email As String = Root1.Get("customer_email")
        Dim payment_intent As String = Root1.Get("payment_intent")
        Dim invoice As String = Root1.Get("invoice")
        Dim cancel_url As String = Root1.Get("cancel_url")
        Dim amount_subtotal As Int = Root1.Get("amount_subtotal")
        Dim object1 As String = Root1.Get("object")
        Dim customer As String = Root1.Get("customer")
        Dim status As String = Root1.Get("status")
      
        Log($"Payment_status: ${payment_status}"$)
        Log($"Status: ${status}"$)

    End If
    jobid.Release
End Sub

Return:
Payment_status: paid
Status: complete
 
Top