Skip to content

Guest checkout with loyalty opt-in

Question: A customer is buying without an account. Your checkout offers to join the loyalty club. What should happen?


No consent, no member. No member, no transaction.

There is no way to “provisionally” enrol someone and tidy it up later. We reject a member without gdpr_accepted: true, and that is the correct behaviour — a purchase is not agreement to marketing.


flowchart TD A["Guest reaches checkout"] --> B["Show loyalty offer<br/>+ consent checkboxes<br/>+ merchant's terms"] B --> C{"Consent ticked?"} C -->|No| D["Take payment.<br/>Post nothing to Diller."] C -->|Yes| E["Take payment"] E --> F["POST /members<br/>with consent"] F --> G["POST /members/{id}/transactions"] G --> H["Customer has points<br/>for this order"] D --> I["Order complete.<br/>No loyalty record."]

Create the member after payment succeeds but before posting the transaction. You need the member id for the transaction, and you do not want a member created for an order that failed.

STORE_ID below is the id you read from GET /stores at setup.

async function completeGuestCheckout(order, loyaltyOptIn) {
const payment = await takePayment(order);
if (!payment.ok) return { ok: false };
if (!loyaltyOptIn?.membershipConsent) {
return { ok: true, memberId: null }; // nothing goes to Diller
}
// They may already be a member from a previous visit.
let member = await findMemberByPhone(order.phone);
if (!member) {
const res = await dillerFetch(`/api/v2.0/stores/${STORE_ID}/members`, {
method: 'POST',
body: JSON.stringify({
phone: splitPhone(order.phone),
first_name: order.firstName,
last_name: order.lastName,
email: order.email,
department_ids: [DEPARTMENT_ID],
consent: {
gdpr_accepted: true,
save_order_history: !!loyaltyOptIn.purchaseHistoryConsent,
receive_sms: !!loyaltyOptIn.smsConsent,
receive_email: !!loyaltyOptIn.emailConsent,
},
origin: ORIGIN,
}),
});
if (res.ok) {
member = await res.json();
} else if (res.status === 400 && /already registered/i.test(await res.text())) {
member = await findMemberByPhone(order.phone); // race: created meanwhile
}
}
if (member) {
await postTransaction(member.id, order);
}
return { ok: true, memberId: member?.id ?? null };
}

The consent checkbox is a legal artefact, not a UI decoration.

  • Unticked by default. A pre-ticked box is not consent.
  • The membership consent is required to join; purchase-history, SMS and email consents are separate and optional.
  • Send all four flags. A marketing flag you leave out of the create request defaults to true, opting the customer in to messages they never chose. The code above sends each one from its own box.
  • Link the merchant’s privacy policy — privacy_policy_url from GET /stores/{storeId}.
  • Use the merchant’s own wording. Do not write your own summary of what they will do with the data.

A workable pattern:

☐ I am joining the customer club at {store}. See the terms (link to privacy_policy_url)

☐ Send me offers by SMS

☐ Send me offers by email

☐ I would like offers and benefits suited to me, based on my preferences and purchase history.

The first is required and maps to gdpr_accepted. The others map to receive_sms, receive_email and save_order_history.

Some merchants’ terms bundle SMS and email marketing into joining the club, with a single box. That is the merchant’s call — but then send receive_sms: true and receive_email: true explicitly when that box is ticked, rather than leaving them out and relying on the default.


  • Create a member with gdpr_accepted: true because “they bought something”. They did not consent.
  • Store the order in Diller under a shared placeholder member. It corrupts the merchant’s reporting and attributes one person’s purchases to a fiction.
  • Silently enrol on a later visit because you recognise the phone number.

See also: Signup and login, Transactions.