Appearance
Add Maytes to your Stripe express row
Put Split with Maytes in your express row, in a full-width row of its own under Apple Pay, Google Pay and Link. It's our button: nothing changes in your Stripe account or your Stripe payment code.
Is this the right page?
Use it to put Maytes under Stripe's wallet buttons. To offer Maytes as a payment method inside Stripe's payment form, see Maytes in your Stripe Payment Element. You can do both.
You add two things: a slot under Stripe's Express Checkout Element, and the Maytes button in it. Your server needs the standard Maytes endpoint and webhook from the checkout button guide.
1. Make room in the express row
Stripe draws its wallets inside its own iframe, so Maytes goes under the Express Checkout Element, not inside it. Stripe's wallet row changes height with the wallets a browser offers, so give Maytes a row of its own instead of a cell beside it:
html
<div class="express-row">
<div id="stripe-express"></div>
<div id="maytes-button"></div>
</div>css
.express-row {
display: grid;
gap: 8px;
}Mount Stripe's element in the first row, with its buttons 48px tall. Leave Stripe's layout at its default, because the number of wallets and rows changes from browser to browser:
js
const express = elements.create('expressCheckout', { buttonHeight: 48 });
express.mount('#stripe-express');2. Add the Maytes button
Render the button in the second row. Three options match it to the wallets: block fills the row, height: 48 matches Stripe's buttonHeight, and radius: 6 matches Stripe's default corners.
html
<script
src="https://js.maytes.co/v1/checkout-button.js"
crossorigin="anonymous"
></script>
<script>
const maytes = window.Maytes({
environment: 'sandbox',
createCheckout: () => createMaytesCheckout('cart_8fd21'),
});
maytes.renderButton(document.getElementById('maytes-button'), {
block: true,
height: 48,
radius: 6,
});
</script>ts
// npm install @maytes/checkout-button
import { Maytes } from '@maytes/checkout-button';
const maytes = Maytes({
environment: 'sandbox',
createCheckout: () => createMaytesCheckout('cart_8fd21'),
});
const slot = document.getElementById('maytes-button')!;
maytes.renderButton(slot, { block: true, height: 48, radius: 6 });tsx
// npm install @maytes/checkout-button
import { ExpressCheckoutElement } from '@stripe/react-stripe-js';
import {
MaytesProvider,
MaytesButton,
} from '@maytes/checkout-button/react';
// Render inside your <Elements> provider.
export function ExpressRow({ cartId }: { cartId: string }) {
return (
<div className="express-row">
<ExpressCheckoutElement
options={{ buttonHeight: 48 }}
onConfirm={confirmWithStripe}
/>
<MaytesProvider
environment="sandbox"
createCheckout={() => createMaytesCheckout(cartId)}
>
<MaytesButton block height={48} radius={6} />
</MaytesProvider>
</div>
);
}tsx
'use client'; // Stripe and Maytes both run in the browser
import { ExpressCheckoutElement } from '@stripe/react-stripe-js';
import {
MaytesProvider,
MaytesButton,
} from '@maytes/checkout-button/react';
// Render inside your <Elements> provider.
export function ExpressRow({ cartId }: { cartId: string }) {
return (
<div className="express-row">
<ExpressCheckoutElement
options={{ buttonHeight: 48 }}
onConfirm={confirmWithStripe}
/>
<MaytesProvider
environment="sandbox"
createCheckout={() => createMaytesCheckout(cartId)}
>
<MaytesButton block height={48} radius={6} />
</MaytesProvider>
</div>
);
}vue
<script setup lang="ts">
import { onBeforeUnmount, onMounted, ref } from 'vue';
import { Maytes, type MaytesSDK } from '@maytes/checkout-button';
const props = defineProps<{ cartId: string }>();
const slot = ref<HTMLDivElement>();
let maytes: MaytesSDK | undefined;
onMounted(() => {
maytes = Maytes({
environment: 'sandbox',
createCheckout: () => createMaytesCheckout(props.cartId),
});
maytes.renderButton(slot.value!, {
block: true,
height: 48,
radius: 6,
});
});
onBeforeUnmount(() => maytes?.destroy());
</script>
<template><div ref="slot" /></template>ts
import {
AfterViewInit,
Component,
ElementRef,
Input,
OnDestroy,
ViewChild,
} from '@angular/core';
import { Maytes, type MaytesSDK } from '@maytes/checkout-button';
@Component({
selector: 'maytes-cell',
standalone: true,
template: '<div #slot></div>',
})
export class MaytesCellComponent implements AfterViewInit, OnDestroy {
@Input() cartId = '';
@ViewChild('slot') slot!: ElementRef<HTMLDivElement>;
private maytes?: MaytesSDK;
ngAfterViewInit() {
this.maytes = Maytes({
environment: 'sandbox',
createCheckout: () => createMaytesCheckout(this.cartId),
});
this.maytes.renderButton(this.slot.nativeElement, {
block: true,
height: 48,
radius: 6,
});
}
ngOnDestroy() {
this.maytes?.destroy();
}
}svelte
<script lang="ts">
import { onMount } from 'svelte';
import { Maytes } from '@maytes/checkout-button';
let { cartId }: { cartId: string } = $props();
let slot: HTMLDivElement;
// onMount runs only in the browser, so SvelteKit's SSR is safe.
onMount(() => {
const maytes = Maytes({
environment: 'sandbox',
createCheckout: () => createMaytesCheckout(cartId),
});
maytes.renderButton(slot, { block: true, height: 48, radius: 6 });
return () => maytes.destroy();
});
</script>
<div bind:this={slot}></div>In Vue, Angular and Svelte, the component above is the Maytes row: place it directly under Stripe's element.
Where createMaytesCheckout comes from
It's a small function on your page that asks your server for the checkout:
js
async function createMaytesCheckout(cartId) {
const res = await fetch('/api/maytes/create-checkout', {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify({ cartId }),
});
if (!res.ok) throw new Error('Could not create the Maytes checkout');
const { checkout_uuid, checkout_url } = await res.json();
return { checkoutId: checkout_uuid, checkoutUrl: checkout_url };
}/api/maytes/create-checkout is a route on your server. It calls Maytes's POST /api/merchant/v1/checkouts with your credentials: see Create the checkout on your server.
Test and go live
- [ ] Keep the button on
environment: 'sandbox', point your server at the sandbox, and pay with a test card. - [ ] Check the row at phone width and with every wallet your browsers offer, such as Apple Pay in Safari and Google Pay in Chrome. Maytes stays full width under them.
- [ ] Switch to
environment: 'production'with production credentials, then follow the go-live checklist.
Troubleshooting
| What you see | What to do |
|---|---|
| Maytes is taller or rounder than the wallets | Match height to Stripe's buttonHeight, and radius to your Stripe appearance's borderRadius |
| Maytes floats between Stripe's rows, or sits beside them | Put Maytes in its own full-width row under Stripe's element, as in step 1. Stripe's wallet row changes height with the wallets a browser offers, so a cell beside it can misalign |
| The order is authorized but never captured | Your webhook must capture within 2 minutes: see Capture on the webhook |
Record Maytes payments in Stripe
A Maytes order doesn't appear in your Stripe Dashboard on its own. To show it there with a Stripe receipt, see Record Maytes payments in Stripe.
Good to know
- Stripe never sees a Maytes order: no PaymentIntent, no Stripe fee, and nothing in your Stripe Dashboard unless you record it. Money arrives through Maytes settlements; refunds go through Maytes refunds.
- Maytes returns no shipping address: show the button where your page already knows the final total.
