#PaulSizer

1 messages · Page 1 of 1 (latest)

hot gorgeBOT
rich dove
#

hey there

#

update their card in their settings

#

Can you explain exactly what you're trying to update?

silk pine
quartz valve
silk pine
marsh timber
#

👋

#

Yep that guide is what you want. It is written for UIKit

#

The STPPaymentCardTextField is very similar to the React Native Card Field

silk pine
#

Perfect, would you say this is the best way to handle what it is I am trying to achieve?

Is the StripePaymentSheet version more for checkouts? Rather than for saving card details?

marsh timber
#

Nope, you can use PaymentSheet to save cards as well. You just use a SetupIntent instead of a PaymentIntent.

#

I certainly recommend PaymentSheet as it gives you easy access to more payment method types

#

But it is mostly up to you and the UI that you are building

silk pine
#

I tried with the PaymentSheet earlier but had a few issues with it. After clicking the button to display the payment sheet it would half come up drop down and then show an error. That was following the docs.

rror Domain=com.stripe.lib Code=50 "There was an unexpected error -- try again in a few seconds" UserInfo={com.stripe.lib:StripeErrorTypeKey=invalid_request_error, NSLocalizedDescription=There was an unexpected error -- try again in a few seconds, com.stripe.lib:StripeErrorCodeKey=resource_missing, com.stripe.lib:ErrorMessageKey=No such setupintent: 'seti_1M47RhGfAYgLoZtyKnwxIkzK', com.stripe.lib:ErrorParameterKey=intent}

marsh timber
#

Can you share your PaymentSheet code?

silk pine
#

Yeah sure, should I just paste it in here?

marsh timber
#

You can put it between three backticks for easy read-ability

#

like so

silk pine
#
import SwiftUI
import StripePaymentSheet

struct ClientCardDetailsView: View {
  @ObservedObject var billingVM: ClientBillingViewModel
    var body: some View {
      ScrollView {
        VStack(alignment: .center, spacing: 8) {
          Text("Current Card Details")
            .foregroundColor(.offWhite)
            .font(.oswaldSubheadline)
          if let billingDetails =  billingVM.billingDetails {
            Text("\(billingDetails.cardBrand.uppercased()) - Ending in \(billingDetails.cardLast4)")
              .font(.latoCaption)
              .foregroundColor(.lightGrey)
          }
          
          if let paymentSheet = billingVM.paymentSheet {
                  PaymentSheet.PaymentButton(
                    paymentSheet: paymentSheet,
                    onCompletion: billingVM.onPaymentCompletion
                  ) {
                    Text("Update")
                  }
                } else {
                  Text("Loading…")
                }
                if let result = billingVM.paymentResult {
                  switch result {
                  case .completed:
                    Text("Payment complete")
                  case .failed(let error):
                    Text("Payment failed: \(error.localizedDescription)")
                  case .canceled:
                    Text("Payment canceled.")
                  }
                }
        }
        .padding()
      }
      .frame(maxWidth: .infinity, maxHeight: .infinity)
      .navigationTitle("CARD DETAILS")
      .navigationBarTitleDisplayMode(.inline)
      .onAppear {
        billingVM.preparePaymentSheet()
      }
    }
}
marsh timber
#

And your preparePaymentSheet() func?

silk pine
#
  func preparePaymentSheet() {
    // MARK: Fetch the SetupIntent and Customer information from the backend
    var request = URLRequest(url: backendCheckoutUrl)
    request.setValue("Bearer \(token ?? "")", forHTTPHeaderField: "Authorization")
    request.setValue("application/json", forHTTPHeaderField: "Content-Type")
    request.httpMethod = "POST"
    let task = URLSession.shared.dataTask(with: request, completionHandler: { [weak self] (data, response, error) in
      guard let data = data,
            let json = try? JSONSerialization.jsonObject(with: data, options: []) as? [String : Any],
            let customerId = json["customer"] as? String,
            let customerEphemeralKeySecret = json["ephemeralKey"] as? String,
            let setupIntentClientSecret = json["setupIntent"] as? String,
            let publishableKey = json["publishableKey"] as? String,
            let self = self else {
        // Handle error
        return
      }
      
      STPAPIClient.shared.publishableKey = publishableKey
      // MARK: Create a PaymentSheet instance
      var configuration = PaymentSheet.Configuration()
      configuration.returnURL = "your-app://stripe-redirect"
      configuration.merchantDisplayName = "Example, Inc."
      configuration.customer = .init(id: customerId, ephemeralKeySecret: customerEphemeralKeySecret)
      configuration.allowsDelayedPaymentMethods = true
      
      DispatchQueue.main.async {
      
        self.paymentSheet = PaymentSheet(setupIntentClientSecret: setupIntentClientSecret, configuration: configuration)
      }
    })
    task.resume()
  }
  
  func onPaymentCompletion(result: PaymentSheetResult) {
    self.paymentResult = result
  }
#

Server

          def payment_sheet            @client = session_user.client            @account_id = @client.coach.organization.stripe_connect_id             @setup_intent = CreatePaymentSheetSetupIntent.new({account_id: @account_id, customer_id: @client.stripe_id}).call            if @setup_intent[:success]              render json: {                setupIntent: @setup_intent[:setupIntent],                ephemeralKey: @setup_intent[:ephemeralKey],                customer: @setup_intent[:customer],                publishableKey: @setup_intent[:publishableKey]              }, status: 201            else              render json: { error: @setup_intent[:error] }, status: 422            end          end
marsh timber
#

Ah this is Connect

silk pine
#
require 'stripe'class CreatePaymentSheetSetupIntent  def initialize(params)    @account_id = params[:account_id]    @customer_id = params[:customer_id]  end  def call     begin      # This will return a Stripe::Charge object      if !@customer_id         customer = Stripe::Customer.create        @customer_id = customer['id']      end                  ephemeralKey = Stripe::EphemeralKey.create({        customer: @customer_id,      },{stripe_account: @account_id, stripe_version: '2022-08-01'})    #   Stripe.api_version = '2022-08-01;automatic_payment_methods_beta=v1'                  setupIntent = Stripe::SetupIntent.create({        customer: @customer_id      }, {stripe_account: @account_id})              return {        setupIntent: setupIntent['client_secret'],        ephemeralKey: ephemeralKey['secret'],        customer: @customer_id,        publishableKey: 'XXX',        success: true      }    rescue Stripe::StripeError => e      # Since it's a decline, Stripe::CardError will be caught      puts "Status is: #{e.http_status}"      puts "Type is: #{e.error.type}"      puts "Code is: #{e.error.code}"       {success: false, error: e.error.message }    end  end  private  def external_setup_intent_service    Stripe::SetupIntent  end  attr_reader :account_id, :descriptionend
marsh timber
silk pine
#

Perfect thank you

marsh timber
#

That fix it?

silk pine
#

I added the following

STPAPIClient.shared.stripeAccount = accountId

under where I set the publishable key bit that didn't fix it

marsh timber
#

Same error?

#

And did you log out the accountId there?

silk pine
#

I think I see whats up, its the account now that was created with a test account and now I'm using the live key.

marsh timber
#

Ah yep that would be an issue

silk pine
marsh timber
#

🎉

silk pine
#

How do I pass the payment method to my backend now?

marsh timber
#

The recommended route is to use Webhooks

#

You would listen for setup_intent.succeeded

#

Or even pass the SetupIntent back to your backend and retrieve the SetupIntent

#

Variety of ways, really.

silk pine
#

Smashing thank you