Skip to main content
The Wallet API provides native mobile payment integration through Apple Pay (iOS) and Google Pay (Android) using Stripe as the payment processor.
This feature requires a Stripe account and proper configuration for Apple Pay and Google Pay.

Usage

Process a payment using native mobile wallets:
use Native\Mobile\Facades\MobileWallet;

// Check if wallet is available
if (MobileWallet::isAvailable()) {
    // Create payment intent
    $intent = MobileWallet::createPaymentIntent(
        amount: 2999, // $29.99 in cents
        currency: 'usd'
    );

    // Present payment sheet
    $result = MobileWallet::presentPaymentSheet(
        clientSecret: $intent->client_secret,
        merchantDisplayName: 'Your Store Name',
        publishableKey: config('services.stripe.key'),
        merchantId: 'merchant.com.yourapp'
    );
}

Checking Availability

Before attempting to use mobile wallets, check if they’re available:
use Native\Mobile\Facades\MobileWallet;

if (MobileWallet::isAvailable()) {
    // Apple Pay or Google Pay is set up and ready
    // Show wallet payment button
} else {
    // Wallet not available on this device
    // Show alternative payment methods
}
A wallet might not be available if:
  • Device doesn’t support it (older devices)
  • User hasn’t set up a payment method in their wallet
  • Feature is disabled in device settings

Payment Flow

The complete payment flow involves several steps:

1. Create Payment Intent

First, create a Stripe payment intent on your server:
$intent = MobileWallet::createPaymentIntent(
    amount: 5000, // $50.00 in cents
    currency: 'usd',
    metadata: [
        'user_id' => auth()->id(),
        'order_id' => $order->id,
    ]
);

$clientSecret = $intent->client_secret;

2. Present Payment Sheet

Show the native payment sheet to the user:
$result = MobileWallet::presentPaymentSheet(
    clientSecret: $clientSecret,
    merchantDisplayName: 'My Store',
    publishableKey: config('services.stripe.publishable_key'),
    merchantId: 'merchant.com.mystore.app',
    merchantCountryCode: 'US',
    options: [
        'allowsDelayedPaymentMethods' => false,
    ]
);

3. Handle Result

The payment sheet is modal - check the result afterwards:
if ($result && isset($result->paymentIntent)) {
    // Payment successful
    $paymentIntentId = $result->paymentIntent->id;
} else {
    // Payment was cancelled or failed
}

4. Confirm Payment (Optional)

For some payment flows, you may need to explicitly confirm:
$confirmation = MobileWallet::confirmPayment($paymentIntentId);

if ($confirmation && $confirmation->status === 'succeeded') {
    // Payment confirmed
}

5. Check Payment Status

Verify the final payment status:
$status = MobileWallet::getPaymentStatus($paymentIntentId);

if ($status && $status->status === 'succeeded') {
    // Process order
}

Methods Reference

isAvailable()

Checks if Apple Pay or Google Pay is available on the device. Returns: bool
$available = MobileWallet::isAvailable();

createPaymentIntent(int $amount, string $currency = 'usd', array $metadata = [])

Creates a Stripe payment intent for the transaction. Parameters:
  • $amount (int) - Amount in smallest currency unit (cents for USD)
  • $currency (string) - Three-letter ISO currency code (lowercase)
  • $metadata (array) - Additional metadata to attach to the payment
Returns: object|null - Payment intent data including client_secret
$intent = MobileWallet::createPaymentIntent(
    amount: 2499,
    currency: 'usd',
    metadata: [
        'order_id' => '12345',
        'customer_id' => 'cust_123',
    ]
);

$clientSecret = $intent->client_secret;

presentPaymentSheet(string $clientSecret, string $merchantDisplayName, string $publishableKey, string $merchantId, string $merchantCountryCode = 'US', array $options = [])

Presents the native payment sheet for card/wallet selection. Parameters:
  • $clientSecret (string) - The client secret from the payment intent
  • $merchantDisplayName (string) - The merchant name to display
  • $publishableKey (string) - The Stripe publishable key
  • $merchantId (string) - The Apple Pay merchant ID (e.g., “merchant.com.yourapp”)
  • $merchantCountryCode (string) - ISO country code (default: “US”)
  • $options (array) - Additional options for the payment sheet
Returns: object|null - Result of presenting the payment sheet
$result = MobileWallet::presentPaymentSheet(
    clientSecret: $intent->client_secret,
    merchantDisplayName: 'Acme Store',
    publishableKey: 'pk_live_...',
    merchantId: 'merchant.com.acmestore',
    merchantCountryCode: 'US',
    options: []
);

confirmPayment(string $paymentIntentId)

Confirms the payment with the selected payment method. Parameters:
  • $paymentIntentId (string) - The payment intent ID to confirm
Returns: object|null - Confirmation result
$confirmation = MobileWallet::confirmPayment('pi_123456');

getPaymentStatus(string $paymentIntentId)

Retrieves the current payment status. Parameters:
  • $paymentIntentId (string) - The payment intent ID to check
Returns: object|null - Payment status information
$status = MobileWallet::getPaymentStatus('pi_123456');

if ($status->status === 'succeeded') {
    // Payment successful
}

Complete Example

use Livewire\Component;
use Livewire\Attributes\On;
use Native\Mobile\Facades\MobileWallet;
use Native\Mobile\Events\Wallet\PaymentCompleted;
use Native\Mobile\Events\Wallet\PaymentFailed;
use Native\Mobile\Events\Wallet\PaymentCancelled;

class Checkout extends Component
{
    public $order;
    public $isProcessing = false;
    public $walletAvailable = false;

    public function mount($orderId)
    {
        $this->order = Order::findOrFail($orderId);
        $this->walletAvailable = MobileWallet::isAvailable();
    }

    public function payWithWallet()
    {
        if (!$this->walletAvailable) {
            session()->flash('error', 'Mobile wallet not available');
            return;
        }

        $this->isProcessing = true;

        try {
            // Create payment intent
            $intent = MobileWallet::createPaymentIntent(
                amount: (int) ($this->order->total * 100), // Convert to cents
                currency: 'usd',
                metadata: [
                    'order_id' => $this->order->id,
                    'user_id' => auth()->id(),
                ]
            );

            if (!$intent) {
                throw new \Exception('Failed to create payment intent');
            }

            // Store payment intent ID for later
            $this->order->update([
                'stripe_payment_intent_id' => $intent->id,
            ]);

            // Present payment sheet
            $result = MobileWallet::presentPaymentSheet(
                clientSecret: $intent->client_secret,
                merchantDisplayName: config('app.name'),
                publishableKey: config('services.stripe.key'),
                merchantId: config('services.stripe.merchant_id'),
                merchantCountryCode: 'US'
            );

            // Check immediate result
            if ($result && isset($result->error)) {
                throw new \Exception($result->error->message ?? 'Payment failed');
            }

        } catch (\Exception $e) {
            $this->isProcessing = false;
            session()->flash('error', 'Payment error: ' . $e->getMessage());
        }
    }

    #[On('native:Native\\Mobile\\Events\\Wallet\\PaymentCompleted')]
    public function handlePaymentCompleted($data)
    {
        $paymentIntentId = $data['paymentIntentId'] ?? null;

        if ($paymentIntentId === $this->order->stripe_payment_intent_id) {
            // Verify payment status
            $status = MobileWallet::getPaymentStatus($paymentIntentId);

            if ($status && $status->status === 'succeeded') {
                // Update order
                $this->order->update([
                    'status' => 'paid',
                    'paid_at' => now(),
                ]);

                $this->isProcessing = false;

                // Redirect to success page
                return redirect()->route('orders.success', $this->order);
            }
        }
    }

    #[On('native:Native\\Mobile\\Events\\Wallet\\PaymentFailed')]
    public function handlePaymentFailed($data)
    {
        $this->isProcessing = false;
        $error = $data['error'] ?? 'Payment failed';
        session()->flash('error', $error);
    }

    #[On('native:Native\\Mobile\\Events\\Wallet\\PaymentCancelled')]
    public function handlePaymentCancelled()
    {
        $this->isProcessing = false;
        session()->flash('message', 'Payment cancelled');
    }

    public function render()
    {
        return view('livewire.checkout');
    }
}

Events

Listen for payment events in your Livewire components:
use Livewire\Attributes\On;

#[On('native:Native\\Mobile\\Events\\Wallet\\PaymentCompleted')]
public function handlePaymentCompleted($data)
{
    $paymentIntentId = $data['paymentIntentId'];
    // Handle successful payment
}

#[On('native:Native\\Mobile\\Events\\Wallet\\PaymentFailed')]
public function handlePaymentFailed($data)
{
    $error = $data['error'];
    // Handle failed payment
}

#[On('native:Native\\Mobile\\Events\\Wallet\\PaymentCancelled')]
public function handlePaymentCancelled()
{
    // User cancelled payment
}

Platform Configuration

iOS (Apple Pay)

Requirements:
  1. Apple Developer account with Apple Pay enabled
  2. Merchant ID configured in Apple Developer portal
  3. Payment processing certificate
  4. App configured with Apple Pay capability
Xcode Configuration:
  1. Enable “Apple Pay” capability
  2. Add merchant ID to entitlements
  3. Configure merchant identifier
Info.plist:
<key>com.apple.developer.in-app-payments</key>
<array>
    <string>merchant.com.yourapp</string>
</array>
Stripe Configuration:
  1. Register merchant ID with Stripe
  2. Configure Apple Pay in Stripe Dashboard
  3. Verify domain ownership

Android (Google Pay)

Requirements:
  1. Google Play Console account
  2. Production app published on Play Store
  3. Google Pay API enabled
  4. Stripe account configured for Google Pay
AndroidManifest.xml:
<meta-data
    android:name="com.google.android.gms.wallet.api.enabled"
    android:value="true" />
Gradle:
dependencies {
    implementation 'com.google.android.gms:play-services-wallet:19.1.0'
    implementation 'com.stripe:stripe-android:20.x.x'
}
Stripe Configuration:
  1. Enable Google Pay in Stripe Dashboard
  2. Add Google Pay merchant ID
  3. Configure payment methods

Currency Support

Specify currency using three-letter ISO codes (lowercase):
// USD (US Dollar)
MobileWallet::createPaymentIntent(1000, 'usd');

// EUR (Euro)
MobileWallet::createPaymentIntent(1000, 'eur');

// GBP (British Pound)
MobileWallet::createPaymentIntent(1000, 'gbp');

// JPY (Japanese Yen - no decimal places)
MobileWallet::createPaymentIntent(1000, 'jpy'); // ¥1,000
Some currencies (like JPY) don’t use decimal places. $10 USD = 1000 cents, but ¥1000 JPY = 1000 (not 100000).

Best Practices

  1. Always Check Availability: Don’t show wallet buttons if not available
@if(MobileWallet::isAvailable())
    <button wire:click="payWithWallet">Pay with Apple/Google Pay</button>
@endif
  1. Handle All States: Listen for completed, failed, and cancelled events
  2. Verify on Server: Always verify payment status on your server before fulfilling orders
  3. Use Metadata: Store order information in payment intent metadata
  4. Provide Feedback: Show loading states during payment processing
  5. Handle Errors Gracefully: Show user-friendly error messages

Testing

iOS Simulator

  • Use test cards in Wallet app
  • Stripe provides test card numbers
  • Requires Xcode sandbox environment

Android Emulator

  • Use test Google accounts
  • Configure test payment methods
  • Requires Google Play Services

Stripe Test Mode

  • Use test publishable key: pk_test_...
  • Use test secret key: sk_test_...
  • Test card: 4242 4242 4242 4242

Build docs developers (and LLMs) love