@itm-studio/partner-sdk 0.3.5 → 0.3.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +381 -1
- package/dist/index.d.mts +547 -4
- package/dist/index.d.ts +547 -4
- package/dist/index.js +1666 -1065
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1666 -1065
- package/dist/index.mjs.map +1 -1
- package/package.json +6 -1
- package/schema/partner.graphql +376 -1
package/README.md
CHANGED
|
@@ -47,6 +47,11 @@ const { getPartnerMomentsForBrand } = await itm.query({
|
|
|
47
47
|
city: true,
|
|
48
48
|
country: true,
|
|
49
49
|
},
|
|
50
|
+
publicVenuePreview: {
|
|
51
|
+
city: true,
|
|
52
|
+
country: true,
|
|
53
|
+
countryCode: true,
|
|
54
|
+
},
|
|
50
55
|
ticketTiers: {
|
|
51
56
|
name: true,
|
|
52
57
|
price: true,
|
|
@@ -515,6 +520,114 @@ The response is always scoped by the partner token; do not pass a brand ID.
|
|
|
515
520
|
`coverImageMobile` and `musicLink` can be `null`, so handle those as optional in
|
|
516
521
|
rendering code.
|
|
517
522
|
|
|
523
|
+
### `getPartnerContributionPages`
|
|
524
|
+
|
|
525
|
+
Get paginated contribution (tip/donation) pages for the brand associated with
|
|
526
|
+
your partner token, newest first. Each page carries its hosted checkout URL
|
|
527
|
+
(`publicUrl`) and confirmed-money totals, so a partner site can link straight
|
|
528
|
+
to checkout and show progress without computing anything itself.
|
|
529
|
+
|
|
530
|
+
Like every contribution operation, this query is guarded for the brand partner
|
|
531
|
+
token (`BrandPartnerGuard`): call it from a `token` client. Brand Agent
|
|
532
|
+
`agentSession` and `agentAccess` clients are not authorized for it and fail
|
|
533
|
+
closed, and the brand is always derived from the token — never pass a brand ID.
|
|
534
|
+
|
|
535
|
+
Supported filters:
|
|
536
|
+
|
|
537
|
+
- `status`: `ACCEPTING`, `PAUSED`, or `ARCHIVED`; omit for all pages.
|
|
538
|
+
- `search`: case-insensitive match over title and slug.
|
|
539
|
+
- `cursor` and `take` (1–100, default 25): use `nextCursor` while `hasNextPage` is true.
|
|
540
|
+
|
|
541
|
+
```typescript
|
|
542
|
+
const { getPartnerContributionPages } = await itm.query({
|
|
543
|
+
getPartnerContributionPages: {
|
|
544
|
+
__args: { status: "ACCEPTING", take: 20 },
|
|
545
|
+
items: {
|
|
546
|
+
uid: true,
|
|
547
|
+
slug: true,
|
|
548
|
+
title: true,
|
|
549
|
+
description: true,
|
|
550
|
+
publicUrl: true, // hosted checkout page on ITM
|
|
551
|
+
qrCodeImageUrl: true, // data URL that resolves to publicUrl
|
|
552
|
+
canContribute: true,
|
|
553
|
+
currency: { code: true, symbol: true },
|
|
554
|
+
suggestedAmountsCents: true,
|
|
555
|
+
recommendedAmountCents: true,
|
|
556
|
+
successfulContributionCount: true,
|
|
557
|
+
grossAmountCents: true,
|
|
558
|
+
netAmountCents: true,
|
|
559
|
+
},
|
|
560
|
+
totalCount: true,
|
|
561
|
+
hasNextPage: true,
|
|
562
|
+
nextCursor: true,
|
|
563
|
+
},
|
|
564
|
+
});
|
|
565
|
+
```
|
|
566
|
+
|
|
567
|
+
`grossAmountCents`, `refundedAmountCents`, and `netAmountCents` are strings
|
|
568
|
+
because totals can exceed a GraphQL `Int`. `canContribute` is `false` while a
|
|
569
|
+
page is paused, archived, or its payout account is not verified; render the
|
|
570
|
+
link only when it is `true`.
|
|
571
|
+
|
|
572
|
+
### `getPartnerContributions`
|
|
573
|
+
|
|
574
|
+
Get paginated contributions for the brand associated with your partner token,
|
|
575
|
+
newest first. This is the feed for a live contributor wall or rolling counter:
|
|
576
|
+
filter `status: "SUCCEEDED"` to list only confirmed contributors, and read
|
|
577
|
+
`note` for the optional message they left at checkout.
|
|
578
|
+
|
|
579
|
+
This query runs behind the same brand partner guard as
|
|
580
|
+
`getPartnerContributionPages`, so it requires a `token` client; Brand Agent
|
|
581
|
+
`agentSession` and `agentAccess` credentials are rejected. It is the
|
|
582
|
+
partner-facing feed only — brand-admin contribution and refund administration
|
|
583
|
+
operations are not part of the partner surface.
|
|
584
|
+
|
|
585
|
+
Supported filters:
|
|
586
|
+
|
|
587
|
+
- `contributionPageUid`: restrict to one page owned by the brand (a page from
|
|
588
|
+
another brand returns a `Contribution page not found` error).
|
|
589
|
+
- `status`: `SUCCEEDED`, `PENDING`, `PROCESSING`, `REVIEW_REQUIRED`, `FAILED`,
|
|
590
|
+
or `EXPIRED`; omit for all statuses. Only `SUCCEEDED` is confirmed money.
|
|
591
|
+
- `search`: case-insensitive match over donor name and email.
|
|
592
|
+
- `cursor` and `take` (1–100, default 25): use `nextCursor` while `hasNextPage` is true.
|
|
593
|
+
|
|
594
|
+
```typescript
|
|
595
|
+
const { getPartnerContributions } = await itm.query({
|
|
596
|
+
getPartnerContributions: {
|
|
597
|
+
__args: {
|
|
598
|
+
contributionPageUid: page.uid,
|
|
599
|
+
status: "SUCCEEDED",
|
|
600
|
+
take: 50,
|
|
601
|
+
cursor: nextCursor,
|
|
602
|
+
},
|
|
603
|
+
items: {
|
|
604
|
+
uid: true,
|
|
605
|
+
donorName: true,
|
|
606
|
+
note: true, // null when the contributor left no note
|
|
607
|
+
amountCents: true,
|
|
608
|
+
currencyCode: true,
|
|
609
|
+
paidAt: true,
|
|
610
|
+
createdAt: true,
|
|
611
|
+
},
|
|
612
|
+
totalCount: true,
|
|
613
|
+
hasNextPage: true,
|
|
614
|
+
nextCursor: true,
|
|
615
|
+
},
|
|
616
|
+
});
|
|
617
|
+
|
|
618
|
+
// Rolling feed: items arrive newest first; totalCount is the number of
|
|
619
|
+
// matching contributions (not distinct donors) for the filter.
|
|
620
|
+
```
|
|
621
|
+
|
|
622
|
+
Refunds do not change `status`: a fully refunded contribution stays
|
|
623
|
+
`SUCCEEDED` with `refundedAmountCents` equal to `amountCents`. For a public
|
|
624
|
+
wall or counter, also skip rows where `refundedAmountCents >= amountCents`.
|
|
625
|
+
`donorName` and `note` are unmoderated contributor input; escape them and apply
|
|
626
|
+
your own moderation before rendering publicly. `donorEmail` is available for
|
|
627
|
+
reconciliation but is personal data; do not render it on public surfaces.
|
|
628
|
+
Amounts are ITM cents (major units × 100, also for zero-decimal currencies) —
|
|
629
|
+
see [Money Amounts](#money-amounts).
|
|
630
|
+
|
|
518
631
|
### `getPartnerMoment`
|
|
519
632
|
|
|
520
633
|
Get a single moment by slug.
|
|
@@ -539,6 +652,11 @@ const { getPartnerMoment } = await itm.query({
|
|
|
539
652
|
city: true,
|
|
540
653
|
postalCode: true,
|
|
541
654
|
},
|
|
655
|
+
publicVenuePreview: {
|
|
656
|
+
city: true,
|
|
657
|
+
country: true,
|
|
658
|
+
countryCode: true,
|
|
659
|
+
},
|
|
542
660
|
ticketTiers: {
|
|
543
661
|
name: true,
|
|
544
662
|
price: true,
|
|
@@ -548,6 +666,15 @@ const { getPartnerMoment } = await itm.query({
|
|
|
548
666
|
});
|
|
549
667
|
```
|
|
550
668
|
|
|
669
|
+
`Moment.venue` returns full venue details and is nullable. For public location
|
|
670
|
+
text, select `Moment.publicVenuePreview`: it returns `city`, a display `country`
|
|
671
|
+
label, and nullable `countryCode`, without the venue name or street address. It
|
|
672
|
+
remains available when the venue is hidden until RSVP, and returns `null` when
|
|
673
|
+
no venue is set. The API field is named `publicVenuePreview`.
|
|
674
|
+
|
|
675
|
+
The same selection works wherever the API returns a `Moment`, including
|
|
676
|
+
`getPartnerMomentsForBrand.moments` and `Ticket.moment`.
|
|
677
|
+
|
|
551
678
|
### `getPublicMomentLineups`
|
|
552
679
|
|
|
553
680
|
Get approved public lineup entries for a moment. Use `getPartnerMoment` first if you only have the moment slug; this query takes the moment `uid`.
|
|
@@ -752,13 +879,20 @@ Supported filters include `ticketTierUids`, `search`, `isActive`, `isPrivate`,
|
|
|
752
879
|
|
|
753
880
|
### `getPartnerTicketsForMoment`
|
|
754
881
|
|
|
755
|
-
Get
|
|
882
|
+
Get confirmed tickets for a moment owned by your partner token's brand. The
|
|
883
|
+
`user` filter matches the current ITM account holding the ticket, rather than
|
|
884
|
+
guest contact details entered at checkout. Provide exactly one of `userUid`,
|
|
885
|
+
`email`, or `phoneNumber`. An unknown account returns an empty ticket page.
|
|
886
|
+
|
|
887
|
+
`take` defaults to 50 and is capped at 100. Continue with `nextCursor` while
|
|
888
|
+
`hasNextPage` is true to retrieve all tickets.
|
|
756
889
|
|
|
757
890
|
```typescript
|
|
758
891
|
const { getPartnerTicketsForMoment } = await itm.query({
|
|
759
892
|
getPartnerTicketsForMoment: {
|
|
760
893
|
__args: {
|
|
761
894
|
momentSlug: "my-event-slug",
|
|
895
|
+
user: { userUid: "customer-uid" }, // optional: exactly one of userUid, email, phoneNumber
|
|
762
896
|
checkedIn: true, // optional: filter by check-in status
|
|
763
897
|
take: 50,
|
|
764
898
|
},
|
|
@@ -766,6 +900,7 @@ const { getPartnerTicketsForMoment } = await itm.query({
|
|
|
766
900
|
uid: true,
|
|
767
901
|
price: true,
|
|
768
902
|
redeemedAt: true,
|
|
903
|
+
encryptedQRCode: true, // render this exact payload as a QR image
|
|
769
904
|
hasExpired: true,
|
|
770
905
|
user: { uid: true, name: true, primaryEmail: true },
|
|
771
906
|
ticketTier: { name: true, price: true },
|
|
@@ -778,6 +913,105 @@ const { getPartnerTicketsForMoment } = await itm.query({
|
|
|
778
913
|
});
|
|
779
914
|
```
|
|
780
915
|
|
|
916
|
+
#### Displaying a purchased ITM ticket in a web or mobile app
|
|
917
|
+
|
|
918
|
+
`Ticket.encryptedQRCode` is the exact opaque payload used by ITM scanners. It is
|
|
919
|
+
a string to encode as a QR image, not an image URL. Render it unchanged: do not
|
|
920
|
+
decrypt it, wrap it in JSON, replace it with the ticket UID, or turn it into a
|
|
921
|
+
URL. Your app does not need ITM's QR encryption key.
|
|
922
|
+
|
|
923
|
+
Keep the SDK and partner token on your Node server. Authenticate the app user
|
|
924
|
+
and resolve their verified ITM account association on that server, then use
|
|
925
|
+
that account's UID (or verified email/phone) in the `user` filter. Do not trust
|
|
926
|
+
an account identifier supplied by the app client: partner tokens grant access
|
|
927
|
+
to the brand's tickets and do not authenticate individual attendees. The token
|
|
928
|
+
only retrieves tickets for its own brand's moments.
|
|
929
|
+
|
|
930
|
+
For example, install `qrcode` in your server application (`npm install qrcode`,
|
|
931
|
+
plus `npm install -D @types/qrcode` for TypeScript), and generate a PNG data URL:
|
|
932
|
+
|
|
933
|
+
```typescript
|
|
934
|
+
import QRCode from "qrcode";
|
|
935
|
+
import { createITMPartnerClient } from "@itm-studio/partner-sdk";
|
|
936
|
+
|
|
937
|
+
const itm = createITMPartnerClient({
|
|
938
|
+
token: process.env.ITM_PARTNER_TOKEN!,
|
|
939
|
+
});
|
|
940
|
+
|
|
941
|
+
// Call only after resolving itmUserUid from the authenticated user's verified
|
|
942
|
+
// account association on your server. It must not come from request parameters.
|
|
943
|
+
async function loadMyMomentTickets(
|
|
944
|
+
itmUserUid: string,
|
|
945
|
+
momentSlug: string,
|
|
946
|
+
cursor?: string,
|
|
947
|
+
) {
|
|
948
|
+
const { getPartnerTicketsForMoment: page } = await itm.query({
|
|
949
|
+
getPartnerTicketsForMoment: {
|
|
950
|
+
__args: {
|
|
951
|
+
momentSlug,
|
|
952
|
+
user: { userUid: itmUserUid },
|
|
953
|
+
take: 50,
|
|
954
|
+
...(cursor ? { cursor } : {}),
|
|
955
|
+
},
|
|
956
|
+
tickets: {
|
|
957
|
+
uid: true,
|
|
958
|
+
encryptedQRCode: true,
|
|
959
|
+
redeemedAt: true,
|
|
960
|
+
hasExpired: true,
|
|
961
|
+
ticketTier: { name: true },
|
|
962
|
+
moment: {
|
|
963
|
+
name: true,
|
|
964
|
+
startDate: true,
|
|
965
|
+
timezone: true,
|
|
966
|
+
publicVenuePreview: { city: true, country: true, countryCode: true },
|
|
967
|
+
},
|
|
968
|
+
},
|
|
969
|
+
hasNextPage: true,
|
|
970
|
+
nextCursor: true,
|
|
971
|
+
},
|
|
972
|
+
});
|
|
973
|
+
|
|
974
|
+
return {
|
|
975
|
+
tickets: await Promise.all(page.tickets.map(async (ticket) => ({
|
|
976
|
+
uid: ticket.uid,
|
|
977
|
+
ticketTierName: ticket.ticketTier.name,
|
|
978
|
+
moment: ticket.moment,
|
|
979
|
+
redeemedAt: ticket.redeemedAt,
|
|
980
|
+
hasExpired: ticket.hasExpired,
|
|
981
|
+
qrCodeImageUrl: await QRCode.toDataURL(ticket.encryptedQRCode, {
|
|
982
|
+
errorCorrectionLevel: "H",
|
|
983
|
+
margin: 4,
|
|
984
|
+
width: 300,
|
|
985
|
+
}),
|
|
986
|
+
}))),
|
|
987
|
+
hasNextPage: page.hasNextPage,
|
|
988
|
+
nextCursor: page.nextCursor,
|
|
989
|
+
};
|
|
990
|
+
}
|
|
991
|
+
```
|
|
992
|
+
|
|
993
|
+
Return this page only to the authenticated owner, with `Cache-Control: private,
|
|
994
|
+
no-store`. A web app can display `ticket.qrCodeImageUrl` as an image:
|
|
995
|
+
|
|
996
|
+
```tsx
|
|
997
|
+
<img src={ticket.qrCodeImageUrl} alt="ITM ticket QR code" width={300} height={300} />
|
|
998
|
+
```
|
|
999
|
+
|
|
1000
|
+
A mobile app can display the same PNG data URL using its image component.
|
|
1001
|
+
Alternatively, return `encryptedQRCode` only to the authenticated owner and
|
|
1002
|
+
pass it unchanged to the app's QR component. Preserve a square image, black
|
|
1003
|
+
modules on a white background, and the white margin; avoid cropping or overlays.
|
|
1004
|
+
|
|
1005
|
+
Fetch fresh ticket data whenever the ticket screen opens or regains focus.
|
|
1006
|
+
Transfers can change the holder and rotate the QR payload, and cancelled or
|
|
1007
|
+
refunded tickets no longer appear in this confirmed-ticket query. Avoid shared
|
|
1008
|
+
caches and application logs containing ticket payloads or images.
|
|
1009
|
+
|
|
1010
|
+
Displaying a QR is separate from admission validation. ITM's scanner checks
|
|
1011
|
+
the current ticket and its applicable admission rules at scan time. Display
|
|
1012
|
+
`redeemedAt` and `hasExpired` as ticket metadata; they are not a substitute for
|
|
1013
|
+
scanner validation, especially for tickets with multiple admissions.
|
|
1014
|
+
|
|
781
1015
|
### `getPartnerCustomersForMoment`
|
|
782
1016
|
|
|
783
1017
|
Get paginated customers (users with their tickets) for a moment.
|
|
@@ -846,6 +1080,117 @@ pages and optional exact counts can be up to 30 seconds old. Consent and
|
|
|
846
1080
|
suppression fields reflect ITM's current brand-scoped records but do not replace
|
|
847
1081
|
your legal-purpose and communications-compliance checks.
|
|
848
1082
|
|
|
1083
|
+
### Membership tiers and members
|
|
1084
|
+
|
|
1085
|
+
`getPartnerMembershipTiers` and `getPartnerMembers` use your partner token to
|
|
1086
|
+
select the brand; neither accepts a brand ID. Both queries use optional `input`
|
|
1087
|
+
with `cursor` and `take` (default 50, maximum 100), returning `totalCount`,
|
|
1088
|
+
`hasNextPage`, and `nextCursor`. These operations require a partner token;
|
|
1089
|
+
adding them to the SDK does not grant access to agent sessions.
|
|
1090
|
+
|
|
1091
|
+
#### `getPartnerMembershipTiers`: fetch every tier
|
|
1092
|
+
|
|
1093
|
+
Unfiltered results include inactive and private tiers, but exclude soft-deleted
|
|
1094
|
+
tiers. Optional filters are `search` (name or slug), `isActive`, `isPublic`, and
|
|
1095
|
+
`passTypes`. Tiers are ordered by `sortOrder`, then creation order.
|
|
1096
|
+
|
|
1097
|
+
```typescript
|
|
1098
|
+
import type { GetPartnerMembershipTiersInput } from "@itm-studio/partner-sdk";
|
|
1099
|
+
|
|
1100
|
+
let tierCursor: string | null = null;
|
|
1101
|
+
do {
|
|
1102
|
+
const tierInput: GetPartnerMembershipTiersInput = { cursor: tierCursor, take: 100 };
|
|
1103
|
+
const { getPartnerMembershipTiers: page } = await itm.query({
|
|
1104
|
+
getPartnerMembershipTiers: {
|
|
1105
|
+
__args: { input: tierInput },
|
|
1106
|
+
membershipTiers: {
|
|
1107
|
+
uid: true, name: true, slug: true, passType: true,
|
|
1108
|
+
currencyCode: true, price: true, yearlyPrice: true, oneTimePrice: true,
|
|
1109
|
+
isActive: true, isPublic: true, activeMemberCount: true,
|
|
1110
|
+
},
|
|
1111
|
+
totalCount: true, hasNextPage: true, nextCursor: true,
|
|
1112
|
+
},
|
|
1113
|
+
});
|
|
1114
|
+
// Process or persist page.membershipTiers on your server.
|
|
1115
|
+
if (!page.hasNextPage) break;
|
|
1116
|
+
if (!page.nextCursor || page.nextCursor === tierCursor) {
|
|
1117
|
+
throw new Error("Missing or repeated membership tier cursor");
|
|
1118
|
+
}
|
|
1119
|
+
tierCursor = page.nextCursor;
|
|
1120
|
+
} while (true);
|
|
1121
|
+
```
|
|
1122
|
+
|
|
1123
|
+
Tier `price`, `yearlyPrice`, and `oneTimePrice` are stored in hundredths of the
|
|
1124
|
+
currency's major unit, including JPY: divide by 100 to display a major-unit
|
|
1125
|
+
amount. These are ITM prices, not Stripe minor-unit amounts.
|
|
1126
|
+
`activeMemberCount` counts ACTIVE and PAST_DUE membership rows, excluding deleted
|
|
1127
|
+
users; it is not a count of distinct people.
|
|
1128
|
+
|
|
1129
|
+
#### `getPartnerMembers`: fetch brand-wide or selected tiers
|
|
1130
|
+
|
|
1131
|
+
Each `members` row represents one membership, identified by its `uid`, with
|
|
1132
|
+
`customer` and `membershipTier` details. A customer may appear more than once;
|
|
1133
|
+
use `customer.uid` to group people when needed. It matches the customer UID
|
|
1134
|
+
returned by `getPartnerAudience`. Members are ordered newest first.
|
|
1135
|
+
|
|
1136
|
+
```typescript
|
|
1137
|
+
import type { GetPartnerMembersInput } from "@itm-studio/partner-sdk";
|
|
1138
|
+
|
|
1139
|
+
// Leave empty for the entire brand. To select tiers, use:
|
|
1140
|
+
// { membershipTierUids: ["tier-uid"] }
|
|
1141
|
+
const memberFilters: GetPartnerMembersInput = {};
|
|
1142
|
+
let memberCursor: string | null = null;
|
|
1143
|
+
do {
|
|
1144
|
+
const memberInput: GetPartnerMembersInput = { ...memberFilters, cursor: memberCursor, take: 100 };
|
|
1145
|
+
const { getPartnerMembers: page } = await itm.query({
|
|
1146
|
+
getPartnerMembers: {
|
|
1147
|
+
__args: { input: memberInput },
|
|
1148
|
+
members: {
|
|
1149
|
+
uid: true, status: true, source: true, membershipNumber: true,
|
|
1150
|
+
quantity: true, purchaseType: true, issuedAt: true,
|
|
1151
|
+
currentPeriodStart: true, currentPeriodEnd: true,
|
|
1152
|
+
cancelAtPeriodEnd: true, remainingUses: true,
|
|
1153
|
+
customer: { uid: true, name: true, email: true, emailVerified: true, phoneNumber: true },
|
|
1154
|
+
membershipTier: { uid: true, name: true, slug: true, passType: true },
|
|
1155
|
+
},
|
|
1156
|
+
totalCount: true, hasNextPage: true, nextCursor: true,
|
|
1157
|
+
},
|
|
1158
|
+
});
|
|
1159
|
+
// Process or persist page.members on your server.
|
|
1160
|
+
if (!page.hasNextPage) break;
|
|
1161
|
+
if (!page.nextCursor || page.nextCursor === memberCursor) {
|
|
1162
|
+
throw new Error("Missing or repeated membership cursor");
|
|
1163
|
+
}
|
|
1164
|
+
memberCursor = page.nextCursor;
|
|
1165
|
+
} while (true);
|
|
1166
|
+
```
|
|
1167
|
+
|
|
1168
|
+
The default includes ACTIVE, PAST_DUE, CANCELLED, EXPIRED, and REFUNDED.
|
|
1169
|
+
PENDING represents unpaid checkout reservations and is excluded unless requested
|
|
1170
|
+
explicitly. To include every status, set `memberFilters.statuses` to
|
|
1171
|
+
`["ACTIVE", "PAST_DUE", "CANCELLED", "EXPIRED", "REFUNDED", "PENDING"]`.
|
|
1172
|
+
An empty or null statuses list also uses the default.
|
|
1173
|
+
|
|
1174
|
+
Additional filters include `search` (name, email, phone, or exact membership
|
|
1175
|
+
number), `sources`, `customerUids`, `cancelAtPeriodEnd`, `createdAtFrom`,
|
|
1176
|
+
`createdAtBefore`, `currentPeriodEndFrom`, and `currentPeriodEndBefore`.
|
|
1177
|
+
`From` dates are inclusive and `Before` dates exclusive. Keep filters identical
|
|
1178
|
+
across pages. Tier and customer UID lists accept at most 100 distinct values;
|
|
1179
|
+
search accepts at most 100 characters. Foreign and nonexistent tier UIDs return
|
|
1180
|
+
the same error.
|
|
1181
|
+
|
|
1182
|
+
Deleted users are excluded. Memberships on soft-deleted tiers remain available,
|
|
1183
|
+
so fetch the brand-wide member listing directly rather than assembling it by
|
|
1184
|
+
looping over listed tiers. Pagination reads live data without a snapshot
|
|
1185
|
+
guarantee: changes during a walk, especially tier reordering, can omit or repeat
|
|
1186
|
+
rows. `remainingUses: null` means unlimited; a null `currentPeriodEnd` alone does
|
|
1187
|
+
not establish whether a membership has no expiry.
|
|
1188
|
+
|
|
1189
|
+
The partner responses expose the allowlisted customer, membership, and tier
|
|
1190
|
+
fields, excluding QR tokens, Stripe/payment identifiers, guest information, and
|
|
1191
|
+
form responses. Both endpoints are limited to 120 requests per minute per
|
|
1192
|
+
partner token.
|
|
1193
|
+
|
|
849
1194
|
### `getPartnerMomentCollection`
|
|
850
1195
|
|
|
851
1196
|
Get a moment collection by slug. Collections group related moments together (e.g. a summer series, a festival lineup). Returns the collection with its moments and links.
|
|
@@ -1913,3 +2258,38 @@ Each requested revision gets a new reservation and brand media asset.
|
|
|
1913
2258
|
Deploy the backend reservation migration and schema before publishing SDK 0.3.3,
|
|
1914
2259
|
then release the Eve and Backstage consumers. These operations do not call an
|
|
1915
2260
|
image provider or consume notification credits.
|
|
2261
|
+
|
|
2262
|
+
|
|
2263
|
+
### Record an off-site contribution
|
|
2264
|
+
|
|
2265
|
+
Partner tokens and brand-agent sessions can use `getPartnerContributionPages` to
|
|
2266
|
+
choose a page, then log money already received outside ITM. The backend derives
|
|
2267
|
+
brand scope from authentication. Name and amount are required; email, phone (with
|
|
2268
|
+
country code), and note are optional. This creates no charge, refund capability,
|
|
2269
|
+
new account, or automatic receipt. Paused pages accept manual records; archived
|
|
2270
|
+
pages reject new records. Amounts use ITM cents (major units × 100, including JPY).
|
|
2271
|
+
|
|
2272
|
+
```ts
|
|
2273
|
+
const result = await itm.mutation({
|
|
2274
|
+
createPartnerManualContribution: {
|
|
2275
|
+
__args: { input: {
|
|
2276
|
+
contributionPageUid: pageUid,
|
|
2277
|
+
currencyCode: pageCurrencyCode, // Prevent stale page currency from changing the amount.
|
|
2278
|
+
requestUid, // Generate once and retain unchanged across retries.
|
|
2279
|
+
name: 'Alex',
|
|
2280
|
+
amountCents: 2500,
|
|
2281
|
+
} },
|
|
2282
|
+
uid: true,
|
|
2283
|
+
source: true, // OFF_SITE
|
|
2284
|
+
status: true, // SUCCEEDED
|
|
2285
|
+
},
|
|
2286
|
+
});
|
|
2287
|
+
```
|
|
2288
|
+
|
|
2289
|
+
`ContributionAdmin.donorEmail` is nullable for name-only manual entries.
|
|
2290
|
+
Set `sendReceipt: true` with an `email` to request a receipt. The backend chooses
|
|
2291
|
+
and freezes `manualReceiptType` (`REGULAR` or `DONATION`) from the brand's current
|
|
2292
|
+
receipt eligibility. Omit it for no receipt. `userUid` is populated when entered
|
|
2293
|
+
email or phone matches a current verified ITM identity; unmatched contacts do not
|
|
2294
|
+
create an account, and conflicting matches are rejected. `source` distinguishes
|
|
2295
|
+
`STRIPE`, `OFF_SITE`, and in-person `POS` independently of payment status.
|