# Build an advanced integration # Advanced integration Learn how to embed a custom Stripe payment form in your website or application. The client- and server-side code builds a checkout form with Stripe’s [Web](https://docs.stripe.com/payments/elements.md) or [Mobile](https://docs.stripe.com/payments/mobile.md) elements to let you accept payments. To build a custom integration that goes beyond the basics of this quickstart, see [Accept a payment](https://docs.stripe.com/payments/accept-a-payment.md?&ui=elements). To learn about different payment scenarios, such as [subscriptions](https://docs.stripe.com/billing/subscriptions/build-subscriptions.md?ui=elements), and other Stripe products, [compare payment integrations](https://docs.stripe.com/payments/online-payments.md#compare-features-and-availability). Stripe has a Payment Element integration that manages tax, discounts, shipping, and currency conversion for you. See the [build a checkout page](https://docs.stripe.com/checkout/custom/quickstart.md) to learn more. ```javascript const express = require("express"); const app = express(); const stripe = require("stripe")('<>'); app.use(express.static("public")); app.use(express.json()); const calculateTax = async (items, currency) => { const taxCalculation = await stripe.tax.calculations.create({ currency, customer_details: { address: { line1: "920 5th Ave", city: "Seattle", state: "WA", postal_code: "98104", country: "US", }, address_source: "shipping", }, line_items: items.map((item) => buildLineItem(item)), }); return taxCalculation; }; const buildLineItem = (item) => { return { amount: item.amount, // Amount in cents reference: item.id, // Unique reference for the item in the scope of the calculation }; }; // Securely calculate the order amount, including tax const calculateOrderAmount = (taxCalculation) => { // Calculate the order total with any exclusive taxes on the server to prevent // people from directly manipulating the amount on the client return taxCalculation.amount_total; }; const calculateOrderAmount = (items) => { // Calculate the order total on the server to prevent // people from directly manipulating the amount on the client let total = 0; items.forEach((item) => { total += item.amount; }); return total; }; const chargeCustomer = async (customerId) => { // Lookup the payment methods available for the customer const paymentMethods = await stripe.paymentMethods.list({ customer: customerId, type: "card", }); try { // Charge the customer and payment method immediately const paymentIntent = await stripe.paymentIntents.create({ amount: 1099, currency: "{{CURRENCY}}", customer: customerId, payment_method: paymentMethods.data[0].id, off_session: true, confirm: true, }); } catch (err) { // Error code will be authentication_required if authentication is needed console.log("Error code is: ", err.code); const paymentIntentRetrieved = await stripe.paymentIntents.retrieve(err.raw.payment_intent.id); console.log("PI retrieved: ", paymentIntentRetrieved.id); } }; app.post("/create-payment-intent", async (req, res) => { const { items } = req.body; // Alternatively, set up a webhook to listen for the payment_intent.succeeded event // and attach the PaymentMethod to a new Customer const customer = await stripe.customers.create(); // Create a Tax Calculation for the items being sold const taxCalculation = await calculateTax(items, '{{CURRENCY}}'); const amount = await calculateOrderAmount(taxCalculation); // Create a PaymentIntent with the order amount and currency const paymentIntent = await stripe.paymentIntents.create({ customer: customer.id, setup_future_usage: "off_session", amount: amount, amount: calculateOrderAmount(items), currency: "{{CURRENCY}}", // In the latest version of the API, specifying the `automatic_payment_methods` parameter is optional because Stripe enables its functionality by default. automatic_payment_methods: { enabled: true, }, metadata: { tax_calculation: taxCalculation.id }, }); res.send({ clientSecret: paymentIntent.client_secret, }); }); // Invoke this method in your webhook handler when `payment_intent.succeeded` webhook is received const handlePaymentIntentSucceeded = async (paymentIntent) => { // Create a Tax Transaction for the successful payment stripe.tax.transactions.createFromCalculation({ calculation: paymentIntent.metadata['tax_calculation'], reference: 'myOrder_123', // Replace with a unique reference from your checkout/order system }); }; app.listen(4242, () => console.log("Node server listening on port 4242!")); ``` ```ruby require 'sinatra' require 'stripe' Stripe.api_key = '<>' set :static, true set :port, 4242 def calculate_tax(items, currency) Stripe::Tax::Calculation.create( currency: currency, customer_details: { address: { line1: '920 5th Ave', city: 'Seattle', state: 'WA', postal_code: '98104', country: 'US', }, address_source: 'shipping', }, line_items: items.map {|item| build_line_item(item) } ) end def build_line_item(item) { amount: item['amount'], # Amount in cents reference: item['id'], # Unique reference for the item in the scope of the calculation } end # Securely calculate the order amount, including tax def calculate_order_amount(tax_calculation) # Calculate the order total with any exclusive taxes on the server to prevent # people from directly manipulating the amount on the client tax_calculation.amount_total end # Securely calculate the order amount def calculate_order_amount(_items) # Calculate the order total on the server to prevent # people from directly manipulating the amount on the client _items.sum {|h| h['amount']} end def charge_customer(customerId) # Lookup the payment methods available for the customer payment_methods = Stripe::PaymentMethod.list( customer: customerId, type: 'card' ) begin # Charge the customer and payment method immediately payment_intent = Stripe::PaymentIntent.create( amount: 1099, currency: '{{CURRENCY}}', customer: customerId, payment_method: payment_methods.data[0]['id'], off_session: true, confirm: true ) rescue Stripe::CardError => e # Error code will be authentication_required if authentication is needed puts "Error is: \#{e.error.code}" payment_intent_id = e.error.payment_intent.id payment_intent = Stripe::PaymentIntent.retrieve(payment_intent_id) puts payment_intent.id end end # An endpoint to start the payment process post '/create-payment-intent' do content_type 'application/json' data = JSON.parse(request.body.read) # Alternatively, set up a webhook to listen for the payment_intent.succeeded event # and attach the PaymentMethod to a new Customer customer = Stripe::Customer.create # Create a Tax Calculation for the items being sold tax_calculation = calculate_tax(data['items'], '{{CURRENCY}}') # Create a PaymentIntent with amount and currency payment_intent = Stripe::PaymentIntent.create( customer: customer['id'], setup_future_usage: 'off_session', amount: calculate_order_amount(tax_calculation), amount: calculate_order_amount(data['items']), currency: '{{CURRENCY}}', # In the latest version of the API, specifying the `automatic_payment_methods` parameter is optional because Stripe enables its functionality by default. automatic_payment_methods: { enabled: true, }, metadata: { tax_calculation: tax_calculation.id }, ) { clientSecret: payment_intent.client_secret, }.to_json end # Invoke this method in your webhook handler when `payment_intent.succeeded` webhook is received def handle_payment_intent_succeeded(payment_intent) # Create a Tax Transaction for the successful payment Stripe::Tax::Transaction.create_from_calculation( calculation: payment_intent.metadata['tax_calculation'], reference: 'myOrder_123', # Replace with a unique reference from your checkout/order system ) end ``` ```python \#! /usr/bin/env python3.6 """ Python 3.6 or newer required. """ import json import os import stripe stripe.api_key = '<>' from flask import Flask, render_template, jsonify, request app = Flask(__name__, static_folder='public', static_url_path='', template_folder='public') def calculate_tax(items, currency): tax_calculation = stripe.tax.Calculation.create( currency= currency, customer_details={ "address": { "line1": "920 5th Ave", "city": "Seattle", "state": "WA", "postal_code": "98104", "country": "US", }, "address_source": "shipping", }, line_items=list(map(build_line_item, items)), ) return tax_calculation def build_line_item(item): return { "amount": item["amount"], # Amount in cents "reference": item["id"], # Unique reference for the item in the scope of the calculation } # Securely calculate the order amount, including tax def calculate_order_amount(items, tax_calculation): # Replace this constant with a calculation of the order's amount # Calculate the order total with any exclusive taxes on the server to prevent # people from directly manipulating the amount on the client order_amount = 1400 order_amount += tax_calculation['tax_amount_exclusive'] return order_amount def calculate_order_amount(items): # Replace this constant with a calculation of the order's amount # Calculate the order total on the server to prevent # people from directly manipulating the amount on the client return 1400 def charge_customer(customer_id): # Lookup the payment methods available for the customer payment_methods = stripe.PaymentMethod.list( customer=customer_id, type='card' ) # Charge the customer and payment method immediately try: stripe.PaymentIntent.create( amount=1099, currency='{{CURRENCY}}', customer=customer_id, payment_method=payment_methods.data[0].id, off_session=True, confirm=True ) except stripe.error.CardError as e: err = e.error # Error code will be authentication_required if authentication is needed print('Code is: %s' % err.code) payment_intent_id = err.payment_intent['id'] payment_intent = stripe.PaymentIntent.retrieve(payment_intent_id) @app.route('/create-payment-intent', methods=['POST']) def create_payment(): # Alternatively, set up a webhook to listen for the payment_intent.succeeded event # and attach the PaymentMethod to a new Customer customer = stripe.Customer.create() try: data = json.loads(request.data) # Create a Tax Calculation for the items being sold tax_calculation = calculate_tax(data['items'], '{{CURRENCY}}') # Create a PaymentIntent with the order amount and currency intent = stripe.PaymentIntent.create( customer=customer['id'], setup_future_usage='off_session', amount=calculate_order_amount(data['items'], tax_calculation), amount=calculate_order_amount(data['items']), currency='{{CURRENCY}}', # In the latest version of the API, specifying the `automatic_payment_methods` parameter is optional because Stripe enables its functionality by default. automatic_payment_methods={ 'enabled': True, }, metadata={ 'tax_calculation': tax_calculation['id'] }, ) return jsonify({ 'clientSecret': intent['client_secret'] }) except Exception as e: return jsonify(error=str(e)), 403 # Invoke this method in your webhook handler when `payment_intent.succeeded` webhook is received def handle_payment_intent_succeeded(payment_intent): # Create a Tax Transaction for the successful payment stripe.tax.Transaction.create_from_calculation( calculation=payment_intent['metadata']['tax_calculation'], reference="myOrder_123", # Replace with a unique reference from your checkout/order system ) if __name__ == '__main__': app.run(port=4242) ``` ```php tax->calculations->create([ 'currency' => $currency, 'customer_details' => [ 'address' => [ 'line1' => '920 5th Ave', 'city' => 'Seattle', 'state' => 'WA', 'postal_code' => '98104', 'country' => 'US', ], 'address_source' => 'shipping', ], 'line_items' => array_map('buildLineItem', $items), ]); return $taxCalculation; } function buildLineItem($item) { return [ 'amount' => $item->amount, // Amount in cents 'reference' => $item->id, // Unique reference for the item in the scope of the calculation ]; } // Securely calculate the order amount, including tax function calculateOrderAmount($taxCalculation) { // Calculate the order total with any exclusive taxes on the server to prevent // people from directly manipulating the amount on the client return $taxCalculation->amount_total; } function calculateOrderAmount(array $items): int { // Calculate the order total on the server to prevent // people from directly manipulating the amount on the client $total = 0; foreach($items as $item) { $total += $item->amount; } return $total; } header('Content-Type: application/json'); try { // retrieve JSON from POST body $jsonStr = file_get_contents('php://input'); $jsonObj = json_decode($jsonStr); // Alternatively, set up a webhook to listen for the payment_intent.succeeded event // and attach the PaymentMethod to a new Customer $customer = $stripe->customers->create(); // Create a Tax Calculation for the items being sold $taxCalculation = calculateTax($stripe, $jsonObj->items, '{{CURRENCY}}'); // Create a PaymentIntent with amount and currency $paymentIntent = $stripe->paymentIntents->create([ 'customer' => $customer->id, 'setup_future_usage' => 'off_session', 'amount' => calculateOrderAmount($jsonObj->items, $taxCalculation), 'amount' => calculateOrderAmount($jsonObj->items), 'currency' => '{{CURRENCY}}', // In the latest version of the API, specifying the `automatic_payment_methods` parameter is optional because Stripe enables its functionality by default. 'automatic_payment_methods' => [ 'enabled' => true, ], 'metadata' => [ 'tax_calculation' => $taxCalculation->id ], ]); $output = [ 'clientSecret' => $paymentIntent->client_secret, ]; echo json_encode($output); } catch (Error $e) { http_response_code(500); echo json_encode(['error' => $e->getMessage()]); } // Invoke this method in your webhook handler when `payment_intent.succeeded` webhook is received function handlePaymentIntentSucceeded($stripe, $paymentIntent) { // Create a Tax Transaction for the successful payment $stripe->tax->transactions->createFromCalculation([ "calculation" => $paymentIntent->metadata['tax_calculation'], "reference" => "myOrder_123", // Replace with a unique reference from your checkout/order system ]); } ``` ```php >'); header('Content-Type: application/json'); try { // retrieve JSON from POST body $jsonStr = file_get_contents('php://input'); $jsonObj = json_decode($jsonStr); // Lookup the payment methods available for the customer $paymentMethods = \Stripe\PaymentMethod::all([ 'customer' => $jsonObj->customer, 'type' => 'card' ]); // Charge the customer and payment method immediately $paymentIntent = \Stripe\PaymentIntent::create([ 'amount' => 1099, 'currency' => '{{CURRENCY}}', 'customer' => $jsonObj->customer, 'payment_method' => $paymentMethods->data[0]->id, 'off_session' => true, 'confirm' => true, ]); echo json_encode([ 'paymentIntent' => $paymentIntent, ]); } catch (\Stripe\Exception\CardException $e) { // Error code will be authentication_required if authentication is needed echo 'Error code is:' . $e->getError()->code; $paymentIntentId = $e->getError()->payment_intent->id; $paymentIntent = \Stripe\PaymentIntent::retrieve($paymentIntentId); } catch (Error $e) { http_response_code(500); echo json_encode(['error' => $e->getMessage()]); } ``` ```php >'; ``` ```go package main import ( "bytes" "encoding/json" "io" "log" "net/http" "github.com/stripe/stripe-go/v82" "github.com/stripe/stripe-go/v82/paymentintent" "github.com/stripe/stripe-go/v82/customer" "github.com/stripe/stripe-go/v82/paymentmethod" "github.com/stripe/stripe-go/v82/tax/calculation" "github.com/stripe/stripe-go/v82/tax/transaction" ) func main() { stripe.Key = "<>" fs := http.FileServer(http.Dir("public")) http.Handle("/", fs) http.HandleFunc("/create-payment-intent", handleCreatePaymentIntent) addr := "localhost:4242" log.Printf("Listening on %s ...", addr) log.Fatal(http.ListenAndServe(addr, nil)) } type item struct { Id string Amount int64 } func calculateTax(items []item, currency stripe.Currency) *stripe.TaxCalculation { var lineItems []*stripe.TaxCalculationLineItemParams for _, item := range items { lineItems = append(lineItems, buildLineItem(item)) } taxCalculationParams := &stripe.TaxCalculationParams{ Currency: stripe.String(string(currency)), CustomerDetails: &stripe.TaxCalculationCustomerDetailsParams{ Address: &stripe.AddressParams{ Line1: stripe.String("920 5th Ave"), City: stripe.String("Seattle"), State: stripe.String("WA"), PostalCode: stripe.String("98104"), Country: stripe.String("US"), }, AddressSource: stripe.String("shipping"), }, LineItems: lineItems, } taxCalculation, _ := calculation.New(taxCalculationParams) return taxCalculation } func buildLineItem(i item) *stripe.TaxCalculationLineItemParams { return &stripe.TaxCalculationLineItemParams{ Amount: stripe.Int64(i.Amount), // Amount in cents Reference: stripe.String(i.Id), // Unique reference for the item in the scope of the calculation } } // Securely calculate the order amount, including tax func calculateOrderAmount(taxCalculation *stripe.TaxCalculation) int64 { // Calculate the order total with any exclusive taxes on the server to prevent // people from directly manipulating the amount on the client return taxCalculation.AmountTotal } func calculateOrderAmount(items []item) int64 { // Calculate the order total on the server to prevent // people from directly manipulating the amount on the client total := 0 for _, item := range items { total += item.Amount } return total; } func chargeCustomer(CustomerID string) { // Lookup the payment methods available for the customer params := &stripe.PaymentMethodListParams{ Customer: stripe.String(CustomerID), Type: stripe.String(string(stripe.PaymentMethodTypeCard)), } i := paymentmethod.List(params) for i.Next() { pm := i.PaymentMethod() } piparams := &stripe.PaymentIntentParams{ Amount: stripe.Int64(1099), Currency: stripe.String(string(stripe.Currency{{CURRENCY}})), Customer: stripe.String(CustomerID), PaymentMethod: stripe.String(pm.ID), Confirm: stripe.Bool(true), OffSession: stripe.Bool(true), } // Charge the customer and payment method immediately _, err := paymentintent.New(piparams) if err != nil { if stripeErr, ok := err.(*stripe.Error); ok { // Error code will be authentication_required if authentication is needed fmt.Printf("Error code: %v", stripeErr.Code) paymentIntentID := stripeErr.PaymentIntent.ID paymentIntent, _ := paymentintent.Get(paymentIntentID, nil) fmt.Printf("PI: %v", paymentIntent.ID) } } } func handleCreatePaymentIntent(w http.ResponseWriter, r *http.Request) { if r.Method != "POST" { http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed) return } var req struct { Items []item `json:"items"` } if err := json.NewDecoder(r.Body).Decode(&req); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) log.Printf("json.NewDecoder.Decode: %v", err) return } // Alternatively, set up a webhook to listen for the payment_intent.succeeded event // and attach the PaymentMethod to a new Customer cparams := &stripe.CustomerParams{} c, _ := customer.New(cparams) // Create a Tax Calculation for the items being sold taxCalculation := calculateTax(req.Items, "{{CURRENCY}}") // Create a PaymentIntent with amount and currency params := &stripe.PaymentIntentParams{ Customer: stripe.String(c.ID), SetupFutureUsage: stripe.String("off_session"), Amount: stripe.Int64(calculateOrderAmount(taxCalculation)), Amount: stripe.Int64(calculateOrderAmount(req.Items)), Currency: stripe.String(string(stripe.Currency{{CURRENCY}})), // In the latest version of the API, specifying the `automatic_payment_methods` parameter is optional because Stripe enables its functionality by default. AutomaticPaymentMethods: &stripe.PaymentIntentAutomaticPaymentMethodsParams{ Enabled: stripe.Bool(true), }, } params.AddMetadata("tax_calculation", taxCalculation.ID) pi, err := paymentintent.New(params) log.Printf("pi.New: %v", pi.ClientSecret) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) log.Printf("pi.New: %v", err) return } writeJSON(w, struct { ClientSecret string `json:"clientSecret"` }{ ClientSecret: pi.ClientSecret, }) } // Invoke this method in your webhook handler when `payment_intent.succeeded` webhook is received func handlePaymentIntentSucceeded(paymentIntent stripe.PaymentIntent) { // Create a Tax Transaction for the successful payment params := &stripe.TaxTransactionCreateFromCalculationParams{ Calculation: stripe.String(paymentIntent.Metadata["tax_calculation"]), Reference: stripe.String("myOrder_123"), // Replace with a unique reference from your checkout/order system }; params.AddExpand("line_items") transaction.CreateFromCalculation(params); } func writeJSON(w http.ResponseWriter, v interface{}) { var buf bytes.Buffer if err := json.NewEncoder(&buf).Encode(v); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) log.Printf("json.NewEncoder.Encode: %v", err) return } w.Header().Set("Content-Type", "application/json") if _, err := io.Copy(w, &buf); err != nil { log.Printf("io.Copy: %v", err) return } } ``` ```csharp using System; using System.Collections.Generic; using Microsoft.AspNetCore; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Newtonsoft.Json; using Stripe; using Stripe.Tax; using System.Linq; namespace StripeExample { public class Program { public static void Main(string[] args) { WebHost.CreateDefaultBuilder(args) .UseUrls("http://0.0.0.0:4242") .UseWebRoot("public") .UseStartup() .Build() .Run(); } } public class Startup { public void ConfigureServices(IServiceCollection services) { services.AddMvc().AddNewtonsoftJson(); } public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { StripeConfiguration.ApiKey = "<>"; if (env.IsDevelopment()) app.UseDeveloperExceptionPage(); app.UseRouting(); app.UseStaticFiles(); app.UseEndpoints(endpoints => endpoints.MapControllers()); } } [Route("create-payment-intent")] [ApiController] public class PaymentIntentApiController : Controller { [HttpPost] public ActionResult Create(PaymentIntentCreateRequest request) { // Alternatively, set up a webhook to listen for the payment_intent.succeeded event // and attach the PaymentMethod to a new Customer var customers = new CustomerService(); var customer = customers.Create(new CustomerCreateOptions()); // Create a Tax Calculation for the items being sold var taxCalculation = CalculateTax(request.Items, "{{CURRENCY}}"); var paymentIntentService = new PaymentIntentService(); var paymentIntent = paymentIntentService.Create(new PaymentIntentCreateOptions { Customer = customer.Id, SetupFutureUsage = "off_session", Amount = CalculateOrderAmount(taxCalculation), Amount = CalculateOrderAmount(request.Items), Currency = "{{CURRENCY}}", // In the latest version of the API, specifying the `automatic_payment_methods` parameter is optional because Stripe enables its functionality by default. AutomaticPaymentMethods = new PaymentIntentAutomaticPaymentMethodsOptions { Enabled = true, }, Metadata = new Dictionary { { "tax_calculation", taxCalculation.Id }, }, }); return Json(new { clientSecret = paymentIntent.ClientSecret }); } // Securely calculate the order amount, including tax [NonAction] public long CalculateOrderAmount(Calculation taxCalculation) { // Calculate the order total with any exclusive taxes on the server to prevent // people from directly manipulating the amount on the client return taxCalculation.AmountTotal; } private long CalculateOrderAmount(Item[] items) { // Calculate the order total on the server to prevent // people from directly manipulating the amount on the client long total = 0; foreach (Item item in items) { total += item.Amount; } return total; } [NonAction] public Calculation CalculateTax(Item[] items, string currency) { var lineItems = items.Select(item => BuildLineItem(item)).ToList(); var calculationCreateOptions = new CalculationCreateOptions { Currency = currency, CustomerDetails = new CalculationCustomerDetailsOptions { Address = new AddressOptions { Line1 = "920 5th Ave", City = "Seattle", State = "WA", PostalCode = "98104", Country = "US", }, AddressSource = "shipping", }, LineItems = lineItems, }; var calculationService = new CalculationService(); var calculation = calculationService.Create(calculationCreateOptions); return calculation; } [NonAction] public CalculationLineItemOptions BuildLineItem(Item item) { return new CalculationLineItemOptions { Amount = item.Amount, // Amount in cents Reference = item.Id, // Unique reference for the item in the scope of the calculation }; } public void ChargeCustomer(string customerId) { // Lookup the payment methods available for the customer var paymentMethods = new PaymentMethodService(); var availableMethods = paymentMethods.List(new PaymentMethodListOptions { Customer = customerId, Type = "card", }); try { // Charge the customer and payment method immediately var paymentIntentService = new PaymentIntentService(); var paymentIntent = paymentIntentService.Create(new PaymentIntentCreateOptions { Amount = 1099, Currency = "{{CURRENCY}}", Customer = customerId, PaymentMethod = availableMethods.Data[0].Id, OffSession = true, Confirm = true }); } catch (StripeException e) { switch (e.StripeError.ErrorType) { case "card_error": // Error code will be authentication_required if authentication is needed Console.WriteLine("Error code: " + e.StripeError.Code); var paymentIntentId = e.StripeError.PaymentIntent.Id; var service = new PaymentIntentService(); var paymentIntent = service.Get(paymentIntentId); Console.WriteLine(paymentIntent.Id); break; default: break; } } } // Invoke this method in your webhook handler when `payment_intent.succeeded` webhook is received [NonAction] public void HandlePaymentIntentSucceeded(PaymentIntent paymentIntent) { // Create a Tax Transaction for the successful payment var transactionCreateOptions = new TransactionCreateFromCalculationOptions { Calculation = paymentIntent.Metadata["tax_calculation"], Reference = "myOrder_123", // Replace with a unique reference from your checkout/order system }; var transactionService = new TransactionService(); transactionService.CreateFromCalculation(transactionCreateOptions); } public class Item { [JsonProperty("id")] public string Id { get; set; } [JsonProperty("Amount")] public long Amount { get; set; } } public class PaymentIntentCreateRequest { [JsonProperty("items")] public Item[] Items { get; set; } } } } ``` ```java package com.stripe.sample; import java.nio.file.Paths; import java.util.List; import java.util.ArrayList; import java.util.Map; import java.util.HashMap; import java.util.stream.Collectors; import static spark.Spark.get; import static spark.Spark.post; import static spark.Spark.staticFiles; import static spark.Spark.port; import com.google.gson.Gson; import com.google.gson.annotations.SerializedName; import com.stripe.Stripe; import com.stripe.model.PaymentIntent; import com.stripe.param.PaymentIntentCreateParams; import com.stripe.model.PaymentMethod; import com.stripe.model.Customer; import com.stripe.model.PaymentMethodCollection; import com.stripe.param.CustomerCreateParams; import com.stripe.param.PaymentMethodListParams; import com.stripe.exception.StripeException; import java.util.Arrays; import com.stripe.model.tax.Calculation; import com.stripe.model.tax.Transaction; import com.stripe.param.tax.CalculationCreateParams; import com.stripe.param.tax.CalculationCreateParams.CustomerDetails; import com.stripe.param.tax.CalculationCreateParams.CustomerDetails.Address; import com.stripe.param.tax.CalculationCreateParams.CustomerDetails.AddressSource; import com.stripe.param.tax.CalculationCreateParams.LineItem; import com.stripe.param.tax.TransactionCreateFromCalculationParams; import com.stripe.exception.StripeException; public class Server { private static Gson gson = new Gson(); static class CreatePaymentItem { @SerializedName("id") String id; public String getId() { return id; } @SerializedName("amount") Long amount; public Long getAmount() { return amount; } } static class CreatePayment { @SerializedName("items") CreatePaymentItem[] items; public CreatePaymentItem[] getItems() { return items; } } static class CreatePaymentResponse { private String clientSecret; public CreatePaymentResponse(String clientSecret) { this.clientSecret = clientSecret; } } static Calculation calculateTax(List items, String currency) throws StripeException { List lineItems = items.stream() .map(Server::buildLineItem) .collect(Collectors.toList()); CalculationCreateParams.Builder createParamsBuilder = CalculationCreateParams.builder() .setCurrency(currency) .setCustomerDetails(CustomerDetails.builder() .setAddress(Address.builder() .setLine1("920 5th Ave") .setCity("Seattle") .setState("WA") .setPostalCode("98104") .setCountry("US") .build()) .setAddressSource(AddressSource.SHIPPING) .build()) .addAllLineItem(lineItems); return Calculation.create(createParamsBuilder.build()); } static LineItem buildLineItem(CreatePaymentItem item) { return LineItem.builder() .setAmount(item.getAmount()) // Amount in cents .setReference(item.getId()) // Unique reference for the item in the scope of the calculation .build(); } // Securely calculate the order amount, including tax static long calculateOrderAmount(Calculation taxCalculation) { // Calculate the order total with any exclusive taxes on the server to prevent // people from directly manipulating the amount on the client return taxCalculation.getAmountTotal(); } static int calculateOrderAmount(CreatePaymentItem[] items) { // Calculate the order total on the server to prevent // people from directly manipulating the amount on the client int total = 0; for (CreatePaymentItem item : items) { total += item.getAmount(); } return total; } // Call this function with the ID of the Customer you want to charge static void chargeCustomer(String customerId) { // Lookup the payment methods available for the customer PaymentMethodListParams listParams = new PaymentMethodListParams.Builder().setCustomer(customerId) .setType(PaymentMethodListParams.Type.CARD).build(); PaymentMethodCollection paymentMethods = PaymentMethod.list(listParams); PaymentIntentCreateParams createParams = new PaymentIntentCreateParams.Builder().setCurrency("{{CURRENCY}}") .setAmount(new Long(1099)) .setPaymentMethod(paymentMethods.getData().get(0).getId()) .setCustomer(customerId) .setConfirm(true) .setOffSession(true) .build(); try { // Charge the customer and payment method immediately PaymentIntent paymentIntent = PaymentIntent.create(createParams); } catch (CardException err) { // Error code will be authentication_required if authentication is needed System.out.println("Error code is : " + e.getCode()); String paymentIntentId = e.getStripeError().getPaymentIntent().getId(); PaymentIntent paymentIntent = PaymentIntent.retrieve(paymentIntentId); System.out.println(paymentIntent.getId()); } } // Invoke this method in your webhook handler when `payment_intent.succeeded` webhook is received static Transaction handlePaymentIntentSucceeded(PaymentIntent paymentIntent) throws StripeException { // Create a Tax Transaction for the successful payment TransactionCreateFromCalculationParams createParams = TransactionCreateFromCalculationParams.builder() .setCalculation(paymentIntent.getMetadata().get("tax_calculation")) .setReference("myOrder_123") // Replace with a unique reference from your checkout/order system .build(); return Transaction.createFromCalculation(createParams); } public static void main(String[] args) { port(4242); staticFiles.externalLocation(Paths.get("public").toAbsolutePath().toString()); Stripe.apiKey = "<>"; post("/create-payment-intent", (request, response) -> { response.type("application/json"); // Alternatively, set up a webhook to listen for the payment_intent.succeeded event // and attach the PaymentMethod to a new Customer CustomerCreateParams customerParams = new CustomerCreateParams.Builder().build(); Customer customer = Customer.create(customerParams); CreatePayment postBody = gson.fromJson(request.body(), CreatePayment.class); // Create a Tax Calculation for the items being sold Calculation taxCalculation = calculateTax(Arrays.asList(postBody.getItems()), "{{CURRENCY}}"); PaymentIntentCreateParams params = PaymentIntentCreateParams.builder() .setCustomer(customer.getId()) .setSetupFutureUsage(PaymentIntentCreateParams.SetupFutureUsage.OFF_SESSION) .setAmount(calculateOrderAmount(taxCalculation)) .setAmount(new Long(calculateOrderAmount(postBody.getItems()))) .setCurrency("{{CURRENCY}}") // In the latest version of the API, specifying the `automatic_payment_methods` parameter is optional because Stripe enables its functionality by default. .setAutomaticPaymentMethods( PaymentIntentCreateParams.AutomaticPaymentMethods .builder() .setEnabled(true) .build() ) .putMetadata("tax_calculation", taxCalculation.getId()) .build(); // Create a PaymentIntent with the order amount and currency PaymentIntent paymentIntent = PaymentIntent.create(params); CreatePaymentResponse paymentResponse = new CreatePaymentResponse(paymentIntent.getClientSecret(), paymentIntent.getId()); return gson.toJson(paymentResponse); }); } } ``` ```javascript import React, { useState, useEffect } from "react"; import { loadStripe } from "@stripe/stripe-js"; import { Elements } from "@stripe/react-stripe-js"; import { BrowserRouter as Router, Routes, Route } from 'react-router-dom'; import CheckoutForm from "./CheckoutForm"; import CompletePage from "./CompletePage"; import "./App.css"; // Make sure to call loadStripe outside of a component’s render to avoid // recreating the Stripe object on every render. const stripePromise = loadStripe("<>"); export default function App() { const [clientSecret, setClientSecret] = useState(""); useEffect(() => { // Create PaymentIntent as soon as the page loads fetch("/create-payment-intent", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ items: [{ id: "xl-tshirt", amount: 1000 }] }), }) .then((res) => res.json()) .then((data) => setClientSecret(data.clientSecret)); }, []); useEffect(() => { // Create PaymentIntent as soon as the page loads fetch("/create.php", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ items: [{ id: "xl-tshirt", amount: 1000 }] }), }) .then((res) => res.json()) .then((data) => setClientSecret(data.clientSecret)); }, []); const appearance = { {{APPEARANCE}} }; // Enable the skeleton loader UI for optimal loading. const loader = 'auto'; return (
{clientSecret && ( } /> } /> )}
); } ``` ```javascript import React, { useEffect, useState } from "react"; import { useStripe, } from "@stripe/react-stripe-js"; import "./App.css"; const SuccessIcon = ; const ErrorIcon = ; const InfoIcon = ; const STATUS_CONTENT_MAP = { succeeded: { text: "Payment succeeded", iconColor: "#30B130", icon: SuccessIcon, }, processing: { text: "Your payment is processing.", iconColor: "#6D6E78", icon: InfoIcon, }, requires_payment_method: { text: "Your payment was not successful, please try again.", iconColor: "#DF1B41", icon: ErrorIcon, }, default: { text: "Something went wrong, please try again.", iconColor: "#DF1B41", icon: ErrorIcon, } }; export default function CompletePage() { const stripe = useStripe(); const [status, setStatus] = useState("default"); const [intentId, setIntentId] = useState(null); useEffect(() => { if (!stripe) { return; } const clientSecret = new URLSearchParams(window.location.search).get( "payment_intent_client_secret" ); if (!clientSecret) { return; } stripe.retrievePaymentIntent(clientSecret).then(({paymentIntent}) => { if (!paymentIntent) { return; } setStatus(paymentIntent.status); setIntentId(paymentIntent.id); }); }, [stripe]); return (
{STATUS_CONTENT_MAP[status].icon}

{STATUS_CONTENT_MAP[status].text}

{intentId &&
id {intentId}
status {status}
} {intentId && View details } Test another
); } ``` ```javascript import React, { useState } from "react"; import { PaymentElement, useStripe, useElements } from "@stripe/react-stripe-js"; export default function CheckoutForm() { const stripe = useStripe(); const elements = useElements(); const [email, setEmail] = useState(''); const [message, setMessage] = useState(null); const [isLoading, setIsLoading] = useState(false); const handleSubmit = async (e) => { e.preventDefault(); if (!stripe || !elements) { // Stripe.js hasn't yet loaded. // Make sure to disable form submission until Stripe.js has loaded. return; } setIsLoading(true); const { error } = await stripe.confirmPayment({ elements, confirmParams: { // Make sure to change this to your payment completion page return_url: "http://localhost:3000/complete", receipt_email: email, }, }); // This point will only be reached if there is an immediate error when // confirming the payment. Otherwise, your customer will be redirected to // your `return_url`. For some payment methods like iDEAL, your customer will // be redirected to an intermediate site first to authorize the payment, then // redirected to the `return_url`. if (error.type === "card_error" || error.type === "validation_error") { setMessage(error.message); } else { setMessage("An unexpected error occurred."); } setIsLoading(false); }; const paymentElementOptions = { layout: "accordion" } return (
setEmail(e.target.value)} placeholder="Enter email address" /> {/* Show any error or success messages */} {message &&
{message}
} ); } ``` ```css .App { font-family: -apple-system, BlinkMacSystemFont, sans-serif; font-size: 16px; -webkit-font-smoothing: antialiased; display: flex; align-items: center; flex-direction: column; height: 100vh; width: 100vw; } form { width: 30vw; min-width: 500px; align-self: center; box-shadow: 0px 0px 0px 0.5px rgba(50, 50, 93, 0.1), 0px 2px 5px 0px rgba(50, 50, 93, 0.1), 0px 1px 1.5px 0px rgba(0, 0, 0, 0.07); border-radius: 7px; padding: 40px; margin-top: auto; margin-bottom: auto; } #email { border-radius: 6px; margin-bottom: 16px; padding: 12px; border: 1px solid rgba(50, 50, 93, 0.1); max-height: 44px; font-size: 16px; width: 100%; background: white; box-sizing: border-box; } #payment-message { color: rgb(105, 115, 134); font-size: 16px; line-height: 20px; padding-top: 12px; text-align: center; } #payment-element { margin-bottom: 24px; } /* Buttons and links */ button { background: #0055DE; font-family: Arial, sans-serif; color: #ffffff; border-radius: 4px; border: 0; padding: 12px 16px; font-size: 16px; font-weight: 600; cursor: pointer; display: block; transition: all 0.2s ease; box-shadow: 0px 4px 5.5px 0px rgba(0, 0, 0, 0.07); width: 100%; } button:hover { filter: contrast(115%); } button:disabled { opacity: 0.5; cursor: default; } /* spinner/processing state, errors */ .spinner, .spinner:before, .spinner:after { border-radius: 50%; } .spinner { color: #ffffff; font-size: 22px; text-indent: -99999px; margin: 0px auto; position: relative; width: 20px; height: 20px; box-shadow: inset 0 0 0 2px; -webkit-transform: translateZ(0); -ms-transform: translateZ(0); transform: translateZ(0); } .spinner:before, .spinner:after { position: absolute; content: ''; } .spinner:before { width: 10.4px; height: 20.4px; background: #0055DE; border-radius: 20.4px 0 0 20.4px; top: -0.2px; left: -0.2px; -webkit-transform-origin: 10.4px 10.2px; transform-origin: 10.4px 10.2px; -webkit-animation: loading 2s infinite ease 1.5s; animation: loading 2s infinite ease 1.5s; } .spinner:after { width: 10.4px; height: 10.2px; background: #0055DE; border-radius: 0 10.2px 10.2px 0; top: -0.1px; left: 10.2px; -webkit-transform-origin: 0px 10.2px; transform-origin: 0px 10.2px; -webkit-animation: loading 2s infinite ease; animation: loading 2s infinite ease; } /* Payment status page */ #payment-status { display: flex; justify-content: center; align-items: center; flex-direction: column; row-gap: 30px; width: 30vw; min-width: 500px; min-height: 380px; align-self: center; box-shadow: 0px 0px 0px 0.5px rgba(50, 50, 93, 0.1), 0px 2px 5px 0px rgba(50, 50, 93, 0.1), 0px 1px 1.5px 0px rgba(0, 0, 0, 0.07); border-radius: 7px; padding: 40px; opacity: 0; animation: fadeInAnimation 1s ease forwards; margin-top: auto; margin-bottom: auto; } #status-icon { display: flex; justify-content: center; align-items: center; height: 40px; width: 40px; border-radius: 50%; } h2 { margin: 0; color: #30313D; text-align: center; } a { text-decoration: none; font-size: 16px; font-weight: 600; font-family: Arial, sans-serif; display: block; } a:hover { filter: contrast(120%); } #details-table { overflow-x: auto; width: 100%; } table { width: 100%; font-size: 14px; border-collapse: collapse; } table tbody tr:first-child td { border-top: 1px solid #E6E6E6; /* Top border */ padding-top: 10px; } table tbody tr:last-child td { border-bottom: 1px solid #E6E6E6; /* Bottom border */ } td { padding-bottom: 10px; } .TableContent { text-align: right; color: #6D6E78; } .TableLabel { font-weight: 600; color: #30313D; } #view-details { color: #0055DE; } #retry-button { text-align: center; background: #0055DE; color: #ffffff; border-radius: 4px; border: 0; padding: 12px 16px; transition: all 0.2s ease; box-shadow: 0px 4px 5.5px 0px rgba(0, 0, 0, 0.07); width: 100%; } @keyframes loading { 0% { -webkit-transform: rotate(0deg); transform: rotate(0deg); } 100% { -webkit-transform: rotate(360deg); transform: rotate(360deg); } } @keyframes fadeInAnimation { to { opacity: 1; } } @media only screen and (max-width: 600px) { form, #payment-status { width: 80vw; min-width: initial; } } ``` ```html Accept a payment
``` ```javascript const stripe = Stripe("<>"); // The items the customer wants to buy const items = [{ id: "xl-tshirt", amount: 1000 }]; let elements; initialize(); document .querySelector("#payment-form") .addEventListener("submit", handleSubmit); // Fetches a payment intent and captures the client secret async function initialize() { const response = await fetch("/create-payment-intent", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ items }), }); const { clientSecret } = await response.json(); const appearance = { {{APPEARANCE}} }; elements = stripe.elements({ appearance, clientSecret }); const paymentElementOptions = { layout: "accordion", }; const paymentElement = elements.create("payment", paymentElementOptions); paymentElement.mount("#payment-element"); } // Fetches a payment intent and captures the client secret async function initialize() { const { clientSecret } = await fetch("/create.php", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ items }), }).then((r) => r.json()); elements = stripe.elements({ clientSecret }); const paymentElementOptions = { layout: "accordion", }; const paymentElement = elements.create("payment", paymentElementOptions); paymentElement.mount("#payment-element"); } async function handleSubmit(e) { e.preventDefault(); setLoading(true); const { error } = await stripe.confirmPayment({ elements, confirmParams: { // Make sure to change this to your payment completion page return_url: "http://localhost:4242/complete.html", receipt_email: document.getElementById("email").value, }, }); // This point will only be reached if there is an immediate error when // confirming the payment. Otherwise, your customer will be redirected to // your `return_url`. For some payment methods like iDEAL, your customer will // be redirected to an intermediate site first to authorize the payment, then // redirected to the `return_url`. if (error.type === "card_error" || error.type === "validation_error") { showMessage(error.message); } else { showMessage("An unexpected error occurred."); } setLoading(false); } async function handleSubmit(e) { e.preventDefault(); setLoading(true); const { error } = await stripe.confirmPayment({ elements, confirmParams: { // Make sure to change this to your payment completion page return_url: "http://localhost:4242/complete.html", receipt_email: document.getElementById("email").value, }, }); // This point will only be reached if there is an immediate error when // confirming the payment. Otherwise, your customer will be redirected to // your `return_url`. For some payment methods like iDEAL, your customer will // be redirected to an intermediate site first to authorize the payment, then // redirected to the `return_url`. if (error.type === "card_error" || error.type === "validation_error") { showMessage(error.message); } else { showMessage("An unexpected error occurred."); } setLoading(false); } // ------- UI helpers ------- function showMessage(messageText) { const messageContainer = document.querySelector("#payment-message"); messageContainer.classList.remove("hidden"); messageContainer.textContent = messageText; setTimeout(function () { messageContainer.classList.add("hidden"); messageContainer.textContent = ""; }, 4000); } // Show a spinner on payment submission function setLoading(isLoading) { if (isLoading) { // Disable the button and show a spinner document.querySelector("#submit").disabled = true; document.querySelector("#spinner").classList.remove("hidden"); document.querySelector("#button-text").classList.add("hidden"); } else { document.querySelector("#submit").disabled = false; document.querySelector("#spinner").classList.add("hidden"); document.querySelector("#button-text").classList.remove("hidden"); } } ``` ```css /* Variables */ * { box-sizing: border-box; } body { font-family: -apple-system, BlinkMacSystemFont, sans-serif; font-size: 16px; -webkit-font-smoothing: antialiased; display: flex; flex-direction: column; justify-content: center; align-content: center; height: 100vh; width: 100vw; } form { width: 30vw; min-width: 500px; align-self: center; box-shadow: 0px 0px 0px 0.5px rgba(50, 50, 93, 0.1), 0px 2px 5px 0px rgba(50, 50, 93, 0.1), 0px 1px 1.5px 0px rgba(0, 0, 0, 0.07); border-radius: 7px; padding: 40px; margin-top: auto; margin-bottom: auto; } .hidden { display: none; } #email { border-radius: 6px; margin-bottom: 16px; padding: 12px; border: 1px solid rgba(50, 50, 93, 0.1); max-height: 44px; font-size: 16px; width: 100%; background: white; box-sizing: border-box; } #payment-message { color: rgb(105, 115, 134); font-size: 16px; line-height: 20px; padding-top: 12px; text-align: center; } #payment-element { margin-bottom: 24px; } /* Buttons and links */ button { background: #0055DE; font-family: Arial, sans-serif; color: #ffffff; border-radius: 4px; border: 0; padding: 12px 16px; font-size: 16px; font-weight: 600; cursor: pointer; display: block; transition: all 0.2s ease; box-shadow: 0px 4px 5.5px 0px rgba(0, 0, 0, 0.07); width: 100%; } button:hover { filter: contrast(115%); } button:disabled { opacity: 0.5; cursor: default; } /* spinner/processing state, errors */ .spinner, .spinner:before, .spinner:after { border-radius: 50%; } .spinner { color: #ffffff; font-size: 22px; text-indent: -99999px; margin: 0px auto; position: relative; width: 20px; height: 20px; box-shadow: inset 0 0 0 2px; -webkit-transform: translateZ(0); -ms-transform: translateZ(0); transform: translateZ(0); } .spinner:before, .spinner:after { position: absolute; content: ""; } .spinner:before { width: 10.4px; height: 20.4px; background: #0055DE; border-radius: 20.4px 0 0 20.4px; top: -0.2px; left: -0.2px; -webkit-transform-origin: 10.4px 10.2px; transform-origin: 10.4px 10.2px; -webkit-animation: loading 2s infinite ease 1.5s; animation: loading 2s infinite ease 1.5s; } .spinner:after { width: 10.4px; height: 10.2px; background: #0055DE; border-radius: 0 10.2px 10.2px 0; top: -0.1px; left: 10.2px; -webkit-transform-origin: 0px 10.2px; transform-origin: 0px 10.2px; -webkit-animation: loading 2s infinite ease; animation: loading 2s infinite ease; } /* Payment status page */ #payment-status { display: flex; justify-content: center; align-items: center; flex-direction: column; row-gap: 30px; width: 30vw; min-width: 500px; min-height: 380px; align-self: center; box-shadow: 0px 0px 0px 0.5px rgba(50, 50, 93, 0.1), 0px 2px 5px 0px rgba(50, 50, 93, 0.1), 0px 1px 1.5px 0px rgba(0, 0, 0, 0.07); border-radius: 7px; padding: 40px; opacity: 0; animation: fadeInAnimation 1s ease forwards; } #status-icon { display: flex; justify-content: center; align-items: center; height: 40px; width: 40px; border-radius: 50%; } h2 { margin: 0; color: #30313D; text-align: center; } a { text-decoration: none; font-size: 16px; font-weight: 600; font-family: Arial, sans-serif; display: block; } a:hover { filter: contrast(120%); } #details-table { overflow-x: auto; width: 100%; } table { width: 100%; font-size: 14px; border-collapse: collapse; } table tbody tr:first-child td { border-top: 1px solid #E6E6E6; /* Top border */ padding-top: 10px; } table tbody tr:last-child td { border-bottom: 1px solid #E6E6E6; /* Bottom border */ } td { padding-bottom: 10px; } .TableContent { text-align: right; color: #6D6E78; } .TableLabel { font-weight: 600; color: #30313D; } #view-details { color: #0055DE; } #retry-button { text-align: center; background: #0055DE; color: #ffffff; border-radius: 4px; border: 0; padding: 12px 16px; transition: all 0.2s ease; box-shadow: 0px 4px 5.5px 0px rgba(0, 0, 0, 0.07); width: 100%; } @-webkit-keyframes loading { 0% { -webkit-transform: rotate(0deg); transform: rotate(0deg); } 100% { -webkit-transform: rotate(360deg); transform: rotate(360deg); } } @keyframes loading { 0% { -webkit-transform: rotate(0deg); transform: rotate(0deg); } 100% { -webkit-transform: rotate(360deg); transform: rotate(360deg); } } @keyframes fadeInAnimation { to { opacity: 1; } } @media only screen and (max-width: 600px) { form, #payment-status{ width: 80vw; min-width: initial; } } ``` ```javascript // ------- UI Resources ------- const SuccessIcon = ` `; const ErrorIcon = ` `; const InfoIcon = ` `; // ------- UI helpers ------- function setPaymentDetails(intent) { let statusText = "Something went wrong, please try again."; let iconColor = "#DF1B41"; let icon = ErrorIcon; if (!intent) { setErrorState(); return; } switch (intent.status) { case "succeeded": statusText = "Payment succeeded"; iconColor = "#30B130"; icon = SuccessIcon; break; case "processing": statusText = "Your payment is processing."; iconColor = "#6D6E78"; icon = InfoIcon; break; case "requires_payment_method": statusText = "Your payment was not successful, please try again."; break; default: break; } document.querySelector("#status-icon").style.backgroundColor = iconColor; document.querySelector("#status-icon").innerHTML = icon; document.querySelector("#status-text").textContent= statusText; document.querySelector("#intent-id").textContent = intent.id; document.querySelector("#intent-status").textContent = intent.status; document.querySelector("#view-details").href = `https://dashboard.stripe.com/payments/${intent.id}`; } function setErrorState() { document.querySelector("#status-icon").style.backgroundColor = "#DF1B41"; document.querySelector("#status-icon").innerHTML = ErrorIcon; document.querySelector("#status-text").textContent= "Something went wrong, please try again."; document.querySelector("#details-table").classList.add("hidden"); document.querySelector("#view-details").classList.add("hidden"); } // Stripe.js instance const stripe = Stripe("<>"); checkStatus(); // Fetches the payment intent status after payment submission async function checkStatus() { const clientSecret = new URLSearchParams(window.location.search).get( "payment_intent_client_secret" ); if (!clientSecret) { setErrorState(); return; } const { paymentIntent } = await stripe.retrievePaymentIntent(clientSecret); setPaymentDetails(paymentIntent); } ``` ```html Order Status ``` ```jsx "use client"; import { useState } from "react"; import { PaymentElement, useStripe, useElements, Elements } from '@stripe/react-stripe-js' import { loadStripe } from '@stripe/stripe-js' // Make sure to call loadStripe outside of a component’s render to avoid // recreating the Stripe object on every render. // This is your test publishable API key. const stripePromise = loadStripe(process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY) function PaymentForm() { const stripe = useStripe(); const elements = useElements(); const [email, setEmail] = useState(''); const [message, setMessage] = useState(null); const [isLoading, setIsLoading] = useState(false); const handleSubmit = async (e) => { e.preventDefault(); if (!stripe || !elements) { // Stripe.js hasn't yet loaded. // Make sure to disable form submission until Stripe.js has loaded. return; } setIsLoading(true); const { error } = await stripe.confirmPayment({ elements, confirmParams: { // Make sure to change this to your payment completion page return_url: "http://localhost:3000/success", receipt_email: email, }, }); // This point will only be reached if there is an immediate error when // confirming the payment. Otherwise, your customer will be redirected to // your `return_url`. For some payment methods like iDEAL, your customer will // be redirected to an intermediate site first to authorize the payment, then // redirected to the `return_url`. if (error.type === "card_error" || error.type === "validation_error") { setMessage(error.message); } else { setMessage("An unexpected error occurred."); } setIsLoading(false); }; const paymentElementOptions = { layout: "accordion", }; return (
setEmail(e.target.value)} placeholder="Enter email address" /> {/* Show any error or success messages */} {message &&
{message}
} ); } export default function CheckoutForm({ clientSecret }) { const appearance = { {{APPEARANCE}} }; return ( ) } ``` ```jsx import { redirect } from 'next/navigation' import { stripe } from '../../lib/stripe' const SuccessIcon = ; const ErrorIcon = ; const InfoIcon = ; const STATUS_CONTENT_MAP = { succeeded: { text: "Payment succeeded", iconColor: "#30B130", icon: SuccessIcon, }, processing: { text: "Your payment is processing.", iconColor: "#6D6E78", icon: InfoIcon, }, requires_payment_method: { text: "Your payment was not successful, please try again.", iconColor: "#DF1B41", icon: ErrorIcon, }, default: { text: "Something went wrong, please try again.", iconColor: "#DF1B41", icon: ErrorIcon, } }; export default async function SuccessPage({ searchParams }) { const { payment_intent: paymentIntentId } = await searchParams if (!paymentIntentId) redirect('/') const paymentIntent = await stripe.paymentIntents.retrieve(paymentIntentId) if (!paymentIntent) redirect('/') const { status } = paymentIntent return (
{STATUS_CONTENT_MAP[status].icon}

{STATUS_CONTENT_MAP[status].text}

{paymentIntent &&
id {paymentIntentId}
status {status}
} {paymentIntent && View details } Test another
); } ``` ```bash \# Stripe keys # https://dashboard.stripe.com/apikeys NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_12345 STRIPE_SECRET_KEY=sk_12345 # Set this environment variable to support webhooks — https://stripe.com/docs/webhooks#verify-events # STRIPE_WEBHOOK_SECRET=whsec_12345 ``` ```bash # https://dashboard.stripe.com/apikeys NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=<> STRIPE_SECRET_KEY=<> # Set this environment variable to support webhooks — https://stripe.com/docs/webhooks#verify-events # STRIPE_WEBHOOK_SECRET=whsec_12345 ``` ```bash .DS_Store .vscode # Node files node_modules/ # Next.js .next .vercel .env .env*.local next-env.d.ts ``` ```css \#__next { align-self: center; } .base { font-family: -apple-system, BlinkMacSystemFont, sans-serif; font-size: 16px; -webkit-font-smoothing: antialiased; display: flex; flex-direction: column; justify-content: center; align-items: center; height: 100vh; width: 100vw; } form { width: 30vw; min-width: 500px; align-self: center; box-shadow: 0px 0px 0px 0.5px rgba(50, 50, 93, 0.1), 0px 2px 5px 0px rgba(50, 50, 93, 0.1), 0px 1px 1.5px 0px rgba(0, 0, 0, 0.07); border-radius: 7px; padding: 40px; margin-top: auto; margin-bottom: auto; } #payment-message { color: rgb(105, 115, 134); font-size: 16px; line-height: 20px; padding-top: 12px; text-align: center; } #payment-element { margin-bottom: 24px; } /* Buttons and links */ button { background: #0055de; font-family: Arial, sans-serif; color: #ffffff; border-radius: 4px; border: 0; padding: 12px 16px; font-size: 16px; font-weight: 600; cursor: pointer; display: block; transition: all 0.2s ease; box-shadow: 0px 4px 5.5px 0px rgba(0, 0, 0, 0.07); width: 100%; } button:hover { filter: contrast(115%); } button:disabled { opacity: 0.5; cursor: default; } /* spinner/processing state, errors */ .spinner, .spinner:before, .spinner:after { border-radius: 50%; } .spinner { color: #ffffff; font-size: 22px; text-indent: -99999px; margin: 0px auto; position: relative; width: 20px; height: 20px; box-shadow: inset 0 0 0 2px; -webkit-transform: translateZ(0); -ms-transform: translateZ(0); transform: translateZ(0); } .spinner:before, .spinner:after { position: absolute; content: ''; } .spinner:before { width: 10.4px; height: 20.4px; background: #0055de; border-radius: 20.4px 0 0 20.4px; top: -0.2px; left: -0.2px; -webkit-transform-origin: 10.4px 10.2px; transform-origin: 10.4px 10.2px; -webkit-animation: loading 2s infinite ease 1.5s; animation: loading 2s infinite ease 1.5s; } .spinner:after { width: 10.4px; height: 10.2px; background: #0055de; border-radius: 0 10.2px 10.2px 0; top: -0.1px; left: 10.2px; -webkit-transform-origin: 0px 10.2px; transform-origin: 0px 10.2px; -webkit-animation: loading 2s infinite ease; animation: loading 2s infinite ease; } /* Payment status page */ #payment-status { display: flex; justify-content: center; align-items: center; flex-direction: column; row-gap: 30px; width: 30vw; min-width: 500px; min-height: 380px; align-self: center; box-shadow: 0px 0px 0px 0.5px rgba(50, 50, 93, 0.1), 0px 2px 5px 0px rgba(50, 50, 93, 0.1), 0px 1px 1.5px 0px rgba(0, 0, 0, 0.07); border-radius: 7px; padding: 40px; opacity: 0; animation: fadeInAnimation 1s ease forwards; } #status-icon { display: flex; justify-content: center; align-items: center; height: 40px; width: 40px; border-radius: 50%; } h2 { margin: 0; color: #30313d; text-align: center; } a { text-decoration: none; font-size: 16px; font-weight: 600; font-family: Arial, sans-serif; display: block; } a:hover { filter: contrast(120%); } #details-table { overflow-x: auto; width: 100%; } table { width: 100%; font-size: 14px; border-collapse: collapse; } table tbody tr:first-child td { border-top: 1px solid #e6e6e6; /* Top border */ padding-top: 10px; } table tbody tr:last-child td { border-bottom: 1px solid #e6e6e6; /* Bottom border */ } td { padding-bottom: 10px; } .TableContent { text-align: right; color: #6d6e78; } .TableLabel { font-weight: 600; color: #30313d; } #view-details { color: #0055de; } #retry-button { text-align: center; background: #0055de; color: #ffffff; border-radius: 4px; border: 0; padding: 12px 16px; transition: all 0.2s ease; box-shadow: 0px 4px 5.5px 0px rgba(0, 0, 0, 0.07); width: 100%; } @keyframes loading { 0% { -webkit-transform: rotate(0deg); transform: rotate(0deg); } 100% { -webkit-transform: rotate(360deg); transform: rotate(360deg); } } @keyframes fadeInAnimation { to { opacity: 1; } } @media only screen and (max-width: 600px) { form, #payment-status { width: 80vw; min-width: initial; } } ``` ```jsx import '../public/global.css' export default function RootLayout({ children }) { return (
{children}
) } ``` ```json { "private": true, "scripts": { "dev": "next", "build": "next build", "start": "next start" }, "dependencies": { "@stripe/react-stripe-js": "^3.1.1", "@stripe/stripe-js": "^5.5.0", "micro": "^10.0.0", "micro-cors": "0.1.1", "next": "15.1.4", "react": "^19.0.0", "react-dom": "^19.0.0", "stripe": "^17.5.0" } } ``` ```jsx import CheckoutForm from '../components/checkout' import { stripe } from '../lib/stripe' export default async function IndexPage() { const calculateTax = async (items, currency) => { const taxCalculation = await stripe.tax.calculations.create({ currency, customer_details: { address: { line1: "920 5th Ave", city: "Seattle", state: "WA", postal_code: "98104", country: "US", }, address_source: "shipping", }, line_items: items.map((item) => buildLineItem(item)), }); return taxCalculation; }; const buildLineItem = (item) => { return { amount: item.amount, // Amount in cents reference: item.id, // Unique reference for the item in the scope of the calculation }; }; // Securely calculate the order amount, including tax const calculateOrderAmount = (items, taxCalculation) => { // Replace this constant with a calculation of the order's amount // Calculate the order total with any exclusive taxes on the server to prevent // people from directly manipulating the amount on the client let orderAmount = 1400; orderAmount += taxCalculation.tax_amount_exclusive; return orderAmount; }; const calculateOrderAmount = (items) => { // Replace this constant with a calculation of the order's amount // Calculate the order total on the server to prevent // people from directly manipulating the amount on the client return 1400; }; const chargeCustomer = async (customerId) => { // Lookup the payment methods available for the customer const paymentMethods = await stripe.paymentMethods.list({ customer: customerId, type: "card", }); try { // Charge the customer and payment method immediately const paymentIntent = await stripe.paymentIntents.create({ amount: 1099, currency: "eur", customer: customerId, payment_method: paymentMethods.data[0].id, off_session: true, confirm: true, }); } catch (err) { // Error code will be authentication_required if authentication is needed console.log("Error code is: ", err.code); const paymentIntentRetrieved = await stripe.paymentIntents.retrieve(err.raw.payment_intent.id); console.log("PI retrieved: ", paymentIntentRetrieved.id); } }; // Alternatively, set up a webhook to listen for the payment_intent.succeeded event // and attach the PaymentMethod to a new Customer const customer = await stripe.customers.create(); const items = [{ id: 'xl-tshirt', amount: 1000 }] const taxCalculation = await calculateTax(items, "eur"); const amount = await calculateOrderAmount(items, taxCalculation); // Create PaymentIntent as soon as the page loads const { client_secret: clientSecret } = await stripe.paymentIntents.create({ customer: customer.id, setup_future_usage: "off_session", amount, amount: calculateOrderAmount([{ id: 'xl-tshirt' }]), currency: 'eur', // In the latest version of the API, specifying the `automatic_payment_methods` parameter is optional because Stripe enables its functionality by default. automatic_payment_methods: { enabled: true, }, metadata: { tax_calculation: taxCalculation.id }, }) return (
) } ``` ```javascript import 'server-only'; import Stripe from 'stripe'; export const stripe = new Stripe(process.env.STRIPE_SECRET_KEY); ``` ```javascript import { NextResponse } from 'next/server' import { headers } from 'next/headers' import { stripe } from '../../../lib/stripe' export async function POST(req) { let event try { event = stripe.webhooks.constructEvent( await req.text(), (await headers()).get('stripe-signature'), process.env.STRIPE_WEBHOOK_SECRET ) } catch (err) { const errorMessage = err.message // On error, log and return the error message. if (err) console.log(err) console.log(`Error message: ${errorMessage}`) return NextResponse.json( { message: `Webhook Error: ${errorMessage}` }, { status: 400 } ) } const permittedEvents = ['payment_intent.succeeded'] if (permittedEvents.includes(event.type)) { let data try { switch (event.type) { case 'payment_intent.succeeded': data = event.data.object console.log(`Payment status: ${data.status}`) // Create a Tax Transaction for the successful payment await stripe.tax.transactions.createFromCalculation({ calculation: data.metadata['tax_calculation'], reference: 'myOrder_123', // Replace with a unique reference from your checkout/order system }); break default: throw new Error(`Unhandled event: ${event.type}`) } } catch (error) { console.log(error) return NextResponse.json( { message: 'Webhook handler failed' }, { status: 500 } ) } } // Return a response to acknowledge receipt of the event. return NextResponse.json({ message: 'Received' }, { status: 200 }) } ``` ```swift import UIKit import StripePaymentSheet class CheckoutViewController: UIViewController { private static let backendURL = URL(string: "http://127.0.0.1:4242")! private var paymentIntentClientSecret: String? private lazy var addressViewController: AddressViewController? = { return AddressViewController(configuration: self.addressConfiguration, delegate: self) }() private var addressDetails: AddressViewController.AddressDetails? private var addressConfiguration: AddressViewController.Configuration { return AddressViewController.Configuration(additionalFields: .init(phone: .optional)) } private lazy var addressButton: UIButton = { let button = UIButton(type: .custom) button.setTitle("Add shipping address", for: .normal) button.backgroundColor = .systemIndigo button.layer.cornerRadius = 5 button.contentEdgeInsets = UIEdgeInsets(top: 12, left: 12, bottom: 12, right: 12) button.addTarget(self, action: #selector(didTapShippingAddressButton), for: .touchUpInside) button.translatesAutoresizingMaskIntoConstraints = false return button }() private lazy var payButton: UIButton = { let button = UIButton(type: .custom) button.setTitle("Pay now", for: .normal) button.backgroundColor = .systemIndigo button.layer.cornerRadius = 5 button.contentEdgeInsets = UIEdgeInsets(top: 12, left: 12, bottom: 12, right: 12) button.addTarget(self, action: #selector(pay), for: .touchUpInside) button.translatesAutoresizingMaskIntoConstraints = false button.isEnabled = false return button }() override func viewDidLoad() { super.viewDidLoad() StripeAPI.defaultPublishableKey = "<>" view.backgroundColor = .systemBackground view.addSubview(payButton) NSLayoutConstraint.activate([ payButton.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 16), payButton.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -16), payButton.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -16) ]) view.addSubview(addressButton) NSLayoutConstraint.activate([ addressButton.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 16), addressButton.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -16), addressButton.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -80) ]) self.fetchPaymentIntent() } func fetchPaymentIntent() { let url = Self.backendURL.appendingPathComponent("/create-payment-intent") let shoppingCartContent: [String: Any] = [ "items": [ ["id": "xl-shirt"] ["id": "xl-shirt", "amount": 1000] ] ] var request = URLRequest(url: url) request.httpMethod = "POST" request.setValue("application/json", forHTTPHeaderField: "Content-Type") request.httpBody = try? JSONSerialization.data(withJSONObject: shoppingCartContent) let task = URLSession.shared.dataTask(with: request, completionHandler: { [weak self] (data, response, error) in guard let response = response as? HTTPURLResponse, response.statusCode == 200, let data = data, let json = try? JSONSerialization.jsonObject(with: data, options: []) as? [String : Any], let clientSecret = json["clientSecret"] as? String else { let message = error?.localizedDescription ?? "Failed to decode response from server." self?.displayAlert(title: "Error loading page", message: message) return } print("Created PaymentIntent") self?.paymentIntentClientSecret = clientSecret DispatchQueue.main.async { self?.payButton.isEnabled = true } }) task.resume() } func displayAlert(title: String, message: String? = nil) { DispatchQueue.main.async { let alertController = UIAlertController(title: title, message: message, preferredStyle: .alert) alertController.addAction(UIAlertAction(title: "OK", style: .default)) self.present(alertController, animated: true) } } @objc func pay() { guard let paymentIntentClientSecret = self.paymentIntentClientSecret else { return } var configuration = PaymentSheet.Configuration() configuration.merchantDisplayName = "Example, Inc." configuration.allowsDelayedPaymentMethods = true configuration.primaryButtonColor = UIColor(red: 1.0, green: 0.82, blue: 0.04, alpha: 1.0) configuration.applePay = .init( merchantId: "com.example.appname", merchantCountryCode: "US" ) configuration.shippingDetails = { [weak self] in return self?.addressDetails } let paymentSheet = PaymentSheet( paymentIntentClientSecret: paymentIntentClientSecret, configuration: configuration) paymentSheet.present(from: self) { [weak self] (paymentResult) in switch paymentResult { case .completed: self?.displayAlert(title: "Payment complete!") case .canceled: print("Payment canceled!") case .failed(let error): self?.displayAlert(title: "Payment failed", message: error.localizedDescription) } } } @objc func didTapShippingAddressButton() { present(UINavigationController(rootViewController: addressViewController!), animated: true) } } // MARK: - AddressViewControllerDelegate extension CheckoutViewController: AddressViewControllerDelegate { func addressViewControllerDidFinish(_ addressViewController: AddressViewController, with address: AddressViewController.AddressDetails?) { addressViewController.dismiss(animated: true) self.addressDetails = address } } ``` ```xml NSCameraUsageDescription Allow the app to scan cards. ``` ```kotlin package com.example.demo import android.app.Application import com.stripe.android.PaymentConfiguration class MyApp : Application() { override fun onCreate() { super.onCreate() PaymentConfiguration.init( applicationContext, "<>" ) } } ``` ```kotlin package com.example.demo import android.os.Bundle import android.widget.Toast import androidx.activity.compose.setContent import androidx.appcompat.app.AppCompatActivity import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.material.AlertDialog import androidx.compose.material.Button import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext import com.stripe.android.PaymentConfiguration import com.stripe.android.paymentsheet.PaymentSheet import com.stripe.android.paymentsheet.PaymentSheetResult import com.stripe.android.paymentsheet.addresselement.AddressDetails import com.stripe.android.paymentsheet.addresselement.AddressLauncher import com.stripe.android.paymentsheet.addresselement.AddressLauncher.AdditionalFieldsConfiguration import com.stripe.android.paymentsheet.addresselement.AddressLauncherResult import com.stripe.android.paymentsheet.addresselement.rememberAddressLauncher import com.stripe.android.paymentsheet.rememberPaymentSheet import okhttp3.* import okhttp3.MediaType.Companion.toMediaType import okhttp3.RequestBody.Companion.toRequestBody import org.json.JSONException import org.json.JSONObject import java.io.IOException import kotlin.coroutines.resume import kotlin.coroutines.suspendCoroutine class CheckoutActivity : AppCompatActivity() { companion object { private const val BACKEND_URL = "http://10.0.2.2:4242" } private val addressConfiguration = AddressLauncher.Configuration.Builder() .additionalFields( AdditionalFieldsConfiguration( AdditionalFieldsConfiguration.FieldConfiguration.REQUIRED ) ) .allowedCountries(setOf("US", "CA", "GB")) .title("Shipping Address") .googlePlacesApiKey("(optional) YOUR KEY HERE") .build() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { CheckoutScreen() } } @Composable private fun CheckoutScreen() { var paymentIntentClientSecret by remember { mutableStateOf(null) } var error by remember { mutableStateOf(null) } val paymentSheet = rememberPaymentSheet { paymentResult -> when (paymentResult) { is PaymentSheetResult.Completed -> showToast("Payment complete!") is PaymentSheetResult.Canceled -> showToast("Payment canceled!") is PaymentSheetResult.Failed -> { error = paymentResult.error.localizedMessage ?: paymentResult.error.message } } } val context = LocalContext.current var shippingDetails by remember { mutableStateOf(null) } val addressLauncher = rememberAddressLauncher { result -> when (result) { is AddressLauncherResult.Succeeded -> { shippingDetails = result.address showToast("Address selection complete!") } is AddressLauncherResult.Canceled -> showToast("Address selection canceled!") } } error?.let { errorMessage -> ErrorAlert( errorMessage = errorMessage, onDismiss = { error = null } ) } LaunchedEffect(Unit) { fetchPaymentIntent().onSuccess { clientSecret -> paymentIntentClientSecret = clientSecret }.onFailure { paymentIntentError -> error = paymentIntentError.localizedMessage ?: paymentIntentError.message } } Column( modifier = Modifier.fillMaxWidth() ) { AddressButton { addressLauncher.present( publishableKey = PaymentConfiguration.getInstance(context).publishableKey, configuration = addressConfiguration ) } PayButton( enabled = paymentIntentClientSecret != null, onClick = { paymentIntentClientSecret?.let { onPayClicked( paymentSheet = paymentSheet, paymentIntentClientSecret = it, shippingDetails = shippingDetails, ) } } ) } PayButton( enabled = paymentIntentClientSecret != null, onClick = { paymentIntentClientSecret?.let { onPayClicked( paymentSheet = paymentSheet, paymentIntentClientSecret = it, ) } } ) } @Composable private fun PayButton( enabled: Boolean, onClick: () -> Unit ) { Button( modifier = Modifier.fillMaxWidth(), enabled = enabled, onClick = onClick ) { Text("Pay now") } } @Composable private fun AddressButton( onClick: () -> Unit ) { Button( modifier = Modifier.fillMaxWidth(), onClick = onClick ) { Text("Add shipping address") } } @Composable private fun ErrorAlert( errorMessage: String, onDismiss: () -> Unit ) { AlertDialog( title = { Text(text = "Error occurred during checkout") }, text = { Text(text = errorMessage) }, onDismissRequest = onDismiss, confirmButton = { Button(onDismiss) { Text(text = "Ok") } } ) } private suspend fun fetchPaymentIntent(): Result = suspendCoroutine { continuation -> val url = "$BACKEND_URL/create-payment-intent" val shoppingCartContent = """ { "items": [ {"id":"xl-tshirt"} {"id":"xl-tshirt", "amount":1000} ] } """ val mediaType = "application/json; charset=utf-8".toMediaType() val body = shoppingCartContent.toRequestBody(mediaType) val request = Request.Builder() .url(url) .post(body) .build() OkHttpClient() .newCall(request) .enqueue(object: Callback { override fun onFailure(call: Call, e: IOException) { continuation.resume(Result.failure(e)) } override fun onResponse(call: Call, response: Response) { if (!response.isSuccessful) { continuation.resume(Result.failure(Exception(response.message))) } else { val clientSecret = extractClientSecretFromResponse(response) clientSecret?.let { secret -> continuation.resume(Result.success(secret)) } ?: run { val error = Exception("Could not find payment intent client secret in response!") continuation.resume(Result.failure(error)) } } } }) } private fun extractClientSecretFromResponse(response: Response): String? { return try { val responseData = response.body?.string() val responseJson = responseData?.let { JSONObject(it) } ?: JSONObject() responseJson.getString("clientSecret") } catch (exception: JSONException) { null } } private fun showToast(message: String) { runOnUiThread { Toast.makeText(this, message, Toast.LENGTH_LONG).show() } } private fun onPayClicked( paymentSheet: PaymentSheet, paymentIntentClientSecret: String, shippingDetails: AddressDetails?, ) { val configuration = PaymentSheet.Configuration.Builder(merchantDisplayName = "Example, Inc.") .shippingDetails(shippingDetails) .allowsDelayedPaymentMethods(true) .appearance( PaymentSheet.Appearance( primaryButton = PaymentSheet.PrimaryButton( colorsLight = PaymentSheet.PrimaryButtonColors( background = Color(red = 248, green = 72, blue = 94), onBackground = Color.White, border = Color.Unspecified ) ) ) ) .googlePay( PaymentSheet.GooglePayConfiguration( environment = PaymentSheet.GooglePayConfiguration.Environment.Test, countryCode = "US" ) ) .build() // Present Payment Sheet paymentSheet.presentWithPaymentIntent(paymentIntentClientSecret, configuration) } } ``` ```xml ... ``` ```java package com.example.demo; import android.app.Application; import com.stripe.android.PaymentConfiguration; public class MyApp extends Application { @Override public void onCreate() { super.onCreate(); PaymentConfiguration.init( getApplicationContext(), "<>" ); } } ``` ```java package com.example.demo; import android.content.res.ColorStateList; import android.graphics.Color; import android.util.Log; import android.os.Bundle; import android.view.View; import android.widget.Button; import android.widget.Toast; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AppCompatActivity; import com.stripe.android.paymentsheet.PaymentSheet; import com.stripe.android.paymentsheet.PaymentSheetResult; import com.stripe.android.paymentsheet.addresselement.AddressDetails; import com.stripe.android.paymentsheet.addresselement.AddressLauncher; import com.stripe.android.paymentsheet.addresselement.AddressLauncherResult; import okhttp3.*; import org.json.JSONException; import org.json.JSONObject; import java.io.IOException; import java.util.Arrays; import java.util.HashSet; public class CheckoutActivity extends AppCompatActivity { private static final String TAG = "CheckoutActivity"; private static final String BACKEND_URL = "http://10.0.2.2:4242"; private String paymentIntentClientSecret; private PaymentSheet paymentSheet; private Button payButton; private AddressLauncher addressLauncher; private AddressDetails shippingDetails; private Button addressButton; private final AddressLauncher.Configuration configuration = new AddressLauncher.Configuration.Builder() .additionalFields( new AddressLauncher.AdditionalFieldsConfiguration( AddressLauncher.AdditionalFieldsConfiguration.FieldConfiguration.REQUIRED ) ) .allowedCountries(new HashSet<>(Arrays.asList("US", "CA", "GB"))) .title("Shipping Address") .googlePlacesApiKey("(optional) YOUR KEY HERE") .build(); @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_checkout); // Hook up the pay button payButton = findViewById(R.id.pay_button); payButton.setOnClickListener(this::onPayClicked); payButton.setEnabled(false); paymentSheet = new PaymentSheet(this, this::onPaymentSheetResult); // Hook up the address button addressButton = findViewById(R.id.address_button); addressButton.setOnClickListener(this::onAddressClicked); addressLauncher = new AddressLauncher(this, this::onAddressLauncherResult); fetchPaymentIntent(); } private void showAlert(String title, @Nullable String message) { runOnUiThread(() -> { AlertDialog dialog = new AlertDialog.Builder(this) .setTitle(title) .setMessage(message) .setPositiveButton("Ok", null) .create(); dialog.show(); }); } private void showToast(String message) { runOnUiThread(() -> Toast.makeText(this, message, Toast.LENGTH_LONG).show()); } private void fetchPaymentIntent() { final String shoppingCartContent = "{\"items\": [ {\"id\":\"xl-tshirt\"}]}"; final String shoppingCartContent = "{\"items\": [ {\"id\":\"xl-tshirt\", \"amount\":1000}]}"; final RequestBody requestBody = RequestBody.create( shoppingCartContent, MediaType.get("application/json; charset=utf-8") ); Request request = new Request.Builder() .url(BACKEND_URL + "/create-payment-intent") .post(requestBody) .build(); new OkHttpClient() .newCall(request) .enqueue(new Callback() { @Override public void onFailure(@NonNull Call call, @NonNull IOException e) { showAlert("Failed to load data", "Error: " + e.toString()); } @Override public void onResponse( @NonNull Call call, @NonNull Response response ) throws IOException { if (!response.isSuccessful()) { showAlert( "Failed to load page", "Error: " + response.toString() ); } else { final JSONObject responseJson = parseResponse(response.body()); paymentIntentClientSecret = responseJson.optString("clientSecret"); runOnUiThread(() -> payButton.setEnabled(true)); Log.i(TAG, "Retrieved PaymentIntent"); } } }); } private JSONObject parseResponse(ResponseBody responseBody) { if (responseBody != null) { try { return new JSONObject(responseBody.string()); } catch (IOException | JSONException e) { Log.e(TAG, "Error parsing response", e); } } return new JSONObject(); } private void onPayClicked(View view) { PaymentSheet.Configuration configuration = new PaymentSheet.Configuration.Builder("Example, Inc.") .allowsDelayedPaymentMethods(true) .primaryButtonColor(ColorStateList.valueOf(Color.rgb(248, 72, 94))) .googlePay(new PaymentSheet.GooglePayConfiguration( PaymentSheet.GooglePayConfiguration.Environment.Test, "US")) .build(); // Present Payment Sheet paymentSheet.presentWithPaymentIntent(paymentIntentClientSecret, configuration); } private void onAddressClicked(View view) { AddressLauncher.Configuration addressConfiguration = new AddressLauncher.Configuration.Builder().address(shippingDetails).build(); String publishableKey = PaymentConfiguration.getInstance(this.getApplicationContext()).getPublishableKey(); addressLauncher.present( publishableKey, addressConfiguration ); } private void onPaymentSheetResult( final PaymentSheetResult paymentSheetResult ) { if (paymentSheetResult instanceof PaymentSheetResult.Completed) { showToast("Payment complete!"); } else if (paymentSheetResult instanceof PaymentSheetResult.Canceled) { Log.i(TAG, "Payment canceled!"); } else if (paymentSheetResult instanceof PaymentSheetResult.Failed) { Throwable error = ((PaymentSheetResult.Failed) paymentSheetResult).getError(); showAlert("Payment failed", error.getLocalizedMessage()); } } private void onAddressLauncherResult(AddressLauncherResult result) { // TODO: Handle result and update your UI if (result instanceof AddressLauncherResult.Succeeded) { shippingDetails = ((AddressLauncherResult.Succeeded) result).getAddress(); } else if (result instanceof AddressLauncherResult.Canceled) { // TODO: Handle cancel } } } ``` ```xml