# Member cards (Secutix contacts)

The Secutix connector isn’t limited to event tickets. It can also sync **Secutix contacts**. This unlocks a “member card in wallet” flow. The pass represents the contact and stays in sync.

## What you can build

You can issue a wallet pass that behaves like a membership card:

* One pass per member (one Secutix contact).
* Barcode / QR code for identification at the venue.
* Automatic updates when contact data changes.
* Optional install/uninstall feedback written back to Secutix.

{% hint style="info" %}
Secutix stays the source of truth for the member profile. Wallet Crew issues and distributes the pass.
{% endhint %}

<details>

<summary>Real-world use cases</summary>

Patterns that work well in production:

* **Membership renewal at the venue**: scan a QR code, update the contact, refresh the pass.
* **Self-service registration**: members sign up from a campaign landing page.
* **Staff-assisted sign-up**: front-office teams use a form instead of Secutix UI.
* **Consent capture**: store opt-in criteria in Secutix during enrolment.
* **Segmented messaging**: use install status to target installed members only.

</details>

## Create and update contacts from an enrolment form

Use a Wallet Crew **enrolment form** to:

* Create a new Secutix contact.
* Update an existing Secutix contact.
* Immediately deliver the member card to Apple Wallet / Google Wallet.

This works well when you don’t want to expose Secutix UI to staff. It also fits self-service registration flows.

### QR code entry point

Display a QR code that opens the enrolment form on a phone. This is ideal for on-site acquisition. Think foyer, ticket desk, or partner booths.

### Mapping: Wallet Crew account ↔ Secutix contact

When contact sync is enabled, Wallet Crew maps **account fields** to **Secutix contact fields**. Customize the mapping when you need to read/write specific Secutix criteria. This is common for consents, segmentation flags, or legacy fields.

Add your custom mapping script in the Secutix configuration **Advanced** tab.

<p align="center"><a href="https://admin.thewalletcrew.io/tenant/~/integrations/secutix/settings" class="button secondary" data-icon="chevrons-right">Secutix configuration screen</a></p>

#### Advanced contact mapping hook

Register `extensions.secutix.contact.mapper`.

* `MapToSecutix(account, request)` runs **before** calling Secutix.
* `MapToNeostore(response, account)` runs **after** Secutix responds.

```javascript
/**
 * Applies enhanced mapping from neostore account data to a Secutix contact request.
 * The standard mapping is already applied on the request object before this function is called.
 *
 * @param {Object} account - Data coming from neostore.
 * @param {Object} request - Request sent to Secutix with default mapping already populated.
 */
function mapToSecutix(account, request){
  request.ExternalContactCriterionData = [{ CriterionIdCode : 'OPT_CO', Values : [account["consents_email"] ? 'true' : 'false'] }];
}

/**
 * Applies enhanced mapping from a Secutix contact response back to a neostore account.
 * The standard mapping is already applied on the account object before this function is called.
 *
 * @param {Object} response - Response received from Secutix.
 * @param {Object} account - Data sent to neostore with default mapping already populated.
 */
function mapToNeostore(response, account){
  response.ContactCriteria = response.ContactCriteria || [];
  account["consents_email"] = !!response.ContactCriteria.find(c => c.CriterionIdCode === 'OPT_CO' && c.Values != null && c.Values[0] === 'true');
}

export default function(context) {
  context.register('extensions.secutix.contact.mapper', {
    MapToSecutix : mapToSecutix,
    MapToNeostore: mapToNeostore
  });
}
```

#### Installation status update on contact

This hook lets you persist pass install/uninstall signals into Secutix contact criteria. Use it to drive segmentation or operational reporting in Secutix.

Implement `extensions.secutix.contact.passUpdater`:

```javascript
/**
 * Triggered when a member card installation status changes.
 *
 * @param {Object} request - Secutix SaveIndividualContactRequest.
 * @param {boolean} isInstalled - `true` if the card is installed, otherwise `false`.
 * @param {number} device - Device on which the installation status changed (0 = Apple, 2 = Google).
 * @returns {Promise<void>} No return value; modifies the request in place.
 */
async function onPassInstallationStatusChanged(request, isInstalled, device){
  request.ExternalContactCriterionData = [
    {
      CriterionIdCode : 'W_' + (device == 0 ? "APPLE" : "GOOGLE"),
      Values : [isInstalled ? 'Installé' : 'Désinstallé']
    }
  ];
}

export default function(context) {
  context.register('extensions.secutix.contact.passUpdater', {
    OnPassInstallationStatusChanged: onPassInstallationStatusChanged
  });
}
```
