In-App Payments SDK: Cookbook

Connect to a Backend Service

Connect a mobile app using the In-App Payments SDK to a server backend.

In-App Payments SDK
Transactions API
Java (Android)
Android
iOS
Save

Before you start

Step 1: Set up a connection to a payment processing backend

To validate and process the nonce returned by the SDK CardEntry object, you must send it to a backend service that uses Connect payment APIs to capture payment.

To set up the backend connection, build a Retrofit object and configure it with the base URL for your backend service:

public class ConfigHelper {

  private static final String CHARGE_SERVER_HOST = "REPLACE_ME";
  private static final String CHARGE_SERVER_URL = "https://" + CHARGE_SERVER_HOST + "/";

  public static boolean serverHostSet() {
    return !CHARGE_SERVER_HOST.equals("REPLACE_ME");
  }

  public static Retrofit createRetrofitInstance() {
    return new Retrofit
        .Builder()
        .baseUrl(ConfigHelper.CHARGE_SERVER_URL)
        .addConverterFactory(MoshiConverterFactory.create())
        .build();
  }
}

To validate and process the nonce returned by the SDK SQIPCardEntryViewController object, you must send it to a backend service that uses Connect payment APIs to capture payment.

To set up the backend connection, declare a static structure and configure it with the base URL for your backend service:

struct Constants {
    struct Square {
        static let APPLICATION_ID: String  = "<# REPLACE_ME #>"
        static let CHARGE_SERVER: String = "REPLACE_ME"
        static let CHARGE_URL: String = "\(CHARGE_SERVER)/chargeForCookie"
    }
}

Step 2: Define a REST interface for POST requests

public interface ChargeService {
  @POST("/chargePaymentCard")
  Call<Void> charge(@Body ChargeRequest request);

  class ChargeErrorResponse {
    String errorMessage;
  }

  class ChargeRequest {
    final String nonce;

    ChargeRequest(String nonce) {
      this.nonce = nonce;
    }
  }
}
class ChargeApi {
    static public func processPayment(
        _ nonce: String, 
        completion: @escaping (String?, String?) -> Void) {
        let url = URL(string: Constants.Square.CHARGE_URL)!
        var request = URLRequest(url: url)
        request.httpMethod = "POST"
        let json = ["nonce": nonce]
        let httpBody = try? JSONSerialization.data(withJSONObject: json)
        request.addValue("Application/json", forHTTPHeaderField: "Content-Type")
        request.httpBody = httpBody
        URLSession.shared.dataTask(with: request) { data, response, error in
            if let error = error as NSError?{
                if error.domain == NSURLErrorDomain {
                    DispatchQueue.main.async {
                        completion("", "Could not contact host")
                    }
                } else {
                    DispatchQueue.main.async {
                        completion("", "Something went wrong")
                    }
                }
            } else if let data = data {
                do {
                    let json = try JSONSerialization.jsonObject(
                        with: data, 
                        options: []) as! [String: Any]
                    if let httpResponse = response as? HTTPURLResponse, 
                    httpResponse.statusCode == 200 {
                        DispatchQueue.main.async {
                            completion("success", nil)
                        }
                    } else {
                        DispatchQueue.main.async {
                            completion("", json["errorMessage"] as? String)
                        }
                    }
                } catch {
                    DispatchQueue.main.async {
                        completion("", "Failure")
                    }
                }
            }
        }.resume()
    }
}

Step 3: Make calls with the REST interface

To use your REST interface, create a Call<ChargeResult> implementation that will package and send requests to the payment processing backend:

public class ChargeCall implements Call<ChargeResult> {

  public static class Factory {
    private final ChargeService service;
    private final Converter<ResponseBody, ChargeService.ChargeErrorResponse> errorConverter;

    public Factory(Retrofit retrofit) {
      service = retrofit.create(ChargeService.class);
      Annotation[] noAnnotations = {};
      Type errorResponseType = ChargeService.ChargeErrorResponse.class;
      errorConverter = retrofit.responseBodyConverter(errorResponseType, noAnnotations);
    }

    public Call<ChargeResult> create(String nonce) {
      return new ChargeCall(this, nonce);
    }
  }

  private final ChargeCall.Factory factory;
  private final String nonce;
  private final retrofit2.Call<Void> call;

  private ChargeCall(ChargeCall.Factory factory,
      String nonce) {
    this.factory = factory;
    this.nonce = nonce;
    call = factory.service.charge(new ChargeService.ChargeRequest(nonce));
  }

  @Override
  public ChargeResult execute() {
    Response<Void> response;
    try {
      response = call.execute();
    } catch (IOException e) {
      return ChargeResult.networkError();
    }
    return responseToResult(response);
  }

  @Override
  public void enqueue(sqip.Callback<ChargeResult> callback) {
    call.enqueue(new Callback<Void>() {
      @Override
      public void onResponse(@NonNull retrofit2.Call<Void> call, @NonNull Response<Void> response) {
        callback.onResult(responseToResult(response));
      }

      @Override
      public void onFailure(@NonNull retrofit2.Call<Void> call, Throwable throwable) {
        if (throwable instanceof IOException) {
          callback.onResult(ChargeResult.networkError());
        } else {
          throw new RuntimeException("Unexpected exception", throwable);
        }
      }
    });
  }

  //TODO: Define the ChargeResult method in step 4.

    //additional required Call<ChargeResult> methods
}

    func cardEntryViewController(
        _ cardEntryViewController: SQIPCardEntryViewController, 
        didObtain cardDetails: SQIPCardDetails, 
        completionHandler: @escaping (Error?) -> Void) {
        ChargeApi.processPayment(
            cardDetails.nonce) { 
                (transactionID, errorDescription) in
           //TODO: Complete stubbed method in Step 4.
        }
    }

Step 4: Handle the backend results

Define a ChargeResult object and act on the results:

private ChargeResult responseToResult(Response<Void> response) {
  if (response.isSuccessful()) {
    return ChargeResult.success();
  }
  try {
    //noinspection ConstantConditions
    ResponseBody errorBody = response.errorBody();
    ChargeService.ChargeErrorResponse errorResponse = factory.errorConverter.convert(errorBody);
    return ChargeResult.error(errorResponse.errorMessage);
  } catch (IOException exception) {
    return ChargeResult.networkError();
  }
}
    func cardEntryViewController(
      _ cardEntryViewController: SQIPCardEntryViewController, 
      didObtain cardDetails: SQIPCardDetails, 
      completionHandler: @escaping (Error?) -> Void) {
        ChargeApi.processPayment(cardDetails.nonce) { 
            (transactionID, errorDescription) in

            //TODO: Complete stubbed method created in Step 3
            guard let errorDescription = errorDescription else {
                // No error occured, we successfully charged
                completionHandler(nil)
                return
            }

            // Pass error description
            let error = NSError(
              domain: "com.example.supercookie", 
              code: 0, 
              userInfo:[NSLocalizedDescriptionKey : errorDescription])
            completionHandler(error)
        }
    }

Contact Developer Support, join our Slack channel, or ask for help on Stack Overflow