@aglyn/plugins-marketplace 1.0.0-beta.201 → 1.0.0-beta.202

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.
Files changed (57) hide show
  1. package/package.json +14 -14
  2. package/src/lib/model/publisher-risk.d.ts +52 -0
  3. package/src/lib/model/publisher-risk.js +74 -0
  4. package/src/lib/model/publisher-risk.js.map +1 -0
  5. package/src/lib/server/billing-webhook.js +72 -4
  6. package/src/lib/server/billing-webhook.js.map +1 -1
  7. package/src/lib/server/checkout.js +18 -1
  8. package/src/lib/server/checkout.js.map +1 -1
  9. package/src/lib/server/connect.js +15 -1
  10. package/src/lib/server/connect.js.map +1 -1
  11. package/src/lib/server/install-dataset-schema.js +3 -1
  12. package/src/lib/server/install-dataset-schema.js.map +1 -1
  13. package/src/lib/server/install-email-starter.js +3 -1
  14. package/src/lib/server/install-email-starter.js.map +1 -1
  15. package/src/lib/server/install-email-template.js +3 -1
  16. package/src/lib/server/install-email-template.js.map +1 -1
  17. package/src/lib/server/install-layout.js +3 -1
  18. package/src/lib/server/install-layout.js.map +1 -1
  19. package/src/lib/server/install-plugin.js +3 -1
  20. package/src/lib/server/install-plugin.js.map +1 -1
  21. package/src/lib/server/install-template.js +3 -1
  22. package/src/lib/server/install-template.js.map +1 -1
  23. package/src/lib/server/install-theme.js +3 -1
  24. package/src/lib/server/install-theme.js.map +1 -1
  25. package/src/lib/server/install.js +3 -1
  26. package/src/lib/server/install.js.map +1 -1
  27. package/src/lib/server/listing-screen.d.ts +83 -0
  28. package/src/lib/server/listing-screen.js +244 -0
  29. package/src/lib/server/listing-screen.js.map +1 -0
  30. package/src/lib/server/publish-dataset-schema.js +12 -0
  31. package/src/lib/server/publish-dataset-schema.js.map +1 -1
  32. package/src/lib/server/publish-email-starter.js +15 -1
  33. package/src/lib/server/publish-email-starter.js.map +1 -1
  34. package/src/lib/server/publish-email-template.js +15 -1
  35. package/src/lib/server/publish-email-template.js.map +1 -1
  36. package/src/lib/server/publish-layout.js +15 -1
  37. package/src/lib/server/publish-layout.js.map +1 -1
  38. package/src/lib/server/publish-plugin.js +41 -3
  39. package/src/lib/server/publish-plugin.js.map +1 -1
  40. package/src/lib/server/publish-template.js +15 -1
  41. package/src/lib/server/publish-template.js.map +1 -1
  42. package/src/lib/server/publish-theme.js +14 -1
  43. package/src/lib/server/publish-theme.js.map +1 -1
  44. package/src/lib/server/publish.js +15 -1
  45. package/src/lib/server/publish.js.map +1 -1
  46. package/src/lib/server/publisher-profile-save.js +36 -0
  47. package/src/lib/server/publisher-profile-save.js.map +1 -1
  48. package/src/lib/server/publisher-review-email.js +15 -5
  49. package/src/lib/server/publisher-review-email.js.map +1 -1
  50. package/src/lib/server/reviews.d.ts +8 -0
  51. package/src/lib/server/reviews.js +43 -3
  52. package/src/lib/server/reviews.js.map +1 -1
  53. package/src/lib/server/sale-risk.d.ts +105 -0
  54. package/src/lib/server/sale-risk.js +315 -0
  55. package/src/lib/server/sale-risk.js.map +1 -0
  56. package/src/lib/server/update-artifact.js +3 -1
  57. package/src/lib/server/update-artifact.js.map +1 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aglyn/plugins-marketplace",
3
- "version": "1.0.0-beta.201",
3
+ "version": "1.0.0-beta.202",
4
4
  "license": "Apache-2.0",
5
5
  "homepage": "https://aglyn.com",
6
6
  "repository": {
@@ -25,19 +25,19 @@
25
25
  "./package.json": "./package.json"
26
26
  },
27
27
  "dependencies": {
28
- "@aglyn/aglyn": "1.0.0-beta.201",
29
- "@aglyn/aglyn-markdown-editor": "1.0.0-beta.201",
30
- "@aglyn/shared-data-enums": "1.0.0-beta.201",
31
- "@aglyn/shared-data-mdi": "1.0.0-beta.201",
32
- "@aglyn/shared-ui-jsx": "1.0.0-beta.201",
33
- "@aglyn/shared-ui-next": "1.0.0-beta.201",
34
- "@aglyn/shared-ui-snackstack": "1.0.0-beta.201",
35
- "@aglyn/shared-util-email": "1.0.0-beta.201",
36
- "@aglyn/shared-util-http": "1.0.0-beta.201",
37
- "@aglyn/shared-util-timestamp": "1.0.0-beta.201",
38
- "@aglyn/tenant-data-admin": "1.0.0-beta.201",
39
- "@aglyn/tenant-feature-instance": "1.0.0-beta.201",
40
- "@aglyn/tenant-runtime": "1.0.0-beta.201",
28
+ "@aglyn/aglyn": "1.0.0-beta.202",
29
+ "@aglyn/aglyn-markdown-editor": "1.0.0-beta.202",
30
+ "@aglyn/shared-data-enums": "1.0.0-beta.202",
31
+ "@aglyn/shared-data-mdi": "1.0.0-beta.202",
32
+ "@aglyn/shared-ui-jsx": "1.0.0-beta.202",
33
+ "@aglyn/shared-ui-next": "1.0.0-beta.202",
34
+ "@aglyn/shared-ui-snackstack": "1.0.0-beta.202",
35
+ "@aglyn/shared-util-email": "1.0.0-beta.202",
36
+ "@aglyn/shared-util-http": "1.0.0-beta.202",
37
+ "@aglyn/shared-util-timestamp": "1.0.0-beta.202",
38
+ "@aglyn/tenant-data-admin": "1.0.0-beta.202",
39
+ "@aglyn/tenant-feature-instance": "1.0.0-beta.202",
40
+ "@aglyn/tenant-runtime": "1.0.0-beta.202",
41
41
  "@swc/helpers": "0.5.23"
42
42
  },
43
43
  "peerDependencies": {
@@ -0,0 +1,52 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+ /** A publisher workspace younger than this many days is in the tier. */
18
+ export declare const YOUNG_PUBLISHER_DAYS = 30;
19
+ /** How long a young publisher's payouts sit in its account. */
20
+ export declare const YOUNG_PUBLISHER_PAYOUT_DELAY_DAYS = 14;
21
+ /** A young publisher's single sale at or above this files a staff signal. */
22
+ export declare const YOUNG_PUBLISHER_REVIEW_SALE_CENTS = 10000;
23
+ /**
24
+ * Is a publisher workspace of this age (days) in the young tier? `null` — a
25
+ * creation date that cannot be read — is an existing customer, as the
26
+ * phishing screen reads it, and is not young.
27
+ */
28
+ export declare function isYoungPublisher(ageDays: number | null | undefined): boolean;
29
+ /**
30
+ * The payout delay a publisher's account should carry: a number of days, or
31
+ * `null` for the account's standard (minimum) schedule.
32
+ */
33
+ export declare function publisherPayoutDelayDays(ageDays: number | null | undefined): number | null;
34
+ /** One reason a sale looks like the publisher paying itself or cashing out cards. */
35
+ export type SaleRiskSignal = {
36
+ code: 'young-publisher-large-sale';
37
+ ageDays: number;
38
+ amountCents: number;
39
+ } | {
40
+ /** The buyer and the publisher share a member (a uid in both workspaces). */
41
+ code: 'shared-member';
42
+ uids: string[];
43
+ } | {
44
+ /**
45
+ * The card that paid was used by ANOTHER buyer workspace to buy from
46
+ * the same publisher — one card, several buyers, one seller.
47
+ */
48
+ code: 'card-reused-across-buyers';
49
+ otherBuyerOrgIds: string[];
50
+ };
51
+ /** One line per signal, in the words staff read on the row. */
52
+ export declare function describeSaleRiskSignals(signals: readonly SaleRiskSignal[]): string[];
@@ -0,0 +1,74 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */ /*==========================================
17
+ * A YOUNG PUBLISHER'S RISK TIER (AGL-3365).
18
+ *
19
+ * The 9/26 actor signed up, paid for a plan on a card that never met 3-D
20
+ * Secure, and within days was sending lures. On the marketplace the same
21
+ * actor has one more move: publish a listing, buy it with stolen cards, and
22
+ * cash the sales out through the payouts the platform sends the publisher.
23
+ *
24
+ * Every marketplace sale is a destination charge — the funds settle on the
25
+ * platform and the publisher's share moves to its connected account at the
26
+ * charge — so the lever the platform keeps is the connected account's PAYOUT
27
+ * SCHEDULE: how long a share sits in the account before it leaves Stripe. A
28
+ * share still in the account can be reversed when a card turns out stolen
29
+ * (the refund and dispute doors already reverse it); a share already paid
30
+ * out to a bank cannot.
31
+ *
32
+ * So a publisher workspace in its first {@link YOUNG_PUBLISHER_DAYS} days has
33
+ * its payouts held {@link YOUNG_PUBLISHER_PAYOUT_DELAY_DAYS} days, which
34
+ * outlasts the window in which issuers send early fraud warnings on most
35
+ * stolen-card charges, and a single sale of
36
+ * {@link YOUNG_PUBLISHER_REVIEW_SALE_CENTS} or more to one files a staff
37
+ * signal while the money can still be reversed. A publisher that ages out of
38
+ * the tier returns to the account's standard schedule on its next sale or its
39
+ * next visit to the payout settings.
40
+ *
41
+ * Age alone, as the phishing screen's tier is: payment history is what the
42
+ * actor bought.
43
+ *=========================================*/ /** A publisher workspace younger than this many days is in the tier. */ export const YOUNG_PUBLISHER_DAYS = 30;
44
+ /** How long a young publisher's payouts sit in its account. */ export const YOUNG_PUBLISHER_PAYOUT_DELAY_DAYS = 14;
45
+ /** A young publisher's single sale at or above this files a staff signal. */ export const YOUNG_PUBLISHER_REVIEW_SALE_CENTS = 10000;
46
+ /**
47
+ * Is a publisher workspace of this age (days) in the young tier? `null` — a
48
+ * creation date that cannot be read — is an existing customer, as the
49
+ * phishing screen reads it, and is not young.
50
+ */ export function isYoungPublisher(ageDays) {
51
+ return typeof ageDays === 'number' && Number.isFinite(ageDays) && ageDays < YOUNG_PUBLISHER_DAYS;
52
+ }
53
+ /**
54
+ * The payout delay a publisher's account should carry: a number of days, or
55
+ * `null` for the account's standard (minimum) schedule.
56
+ */ export function publisherPayoutDelayDays(ageDays) {
57
+ return isYoungPublisher(ageDays) ? YOUNG_PUBLISHER_PAYOUT_DELAY_DAYS : null;
58
+ }
59
+ /** One line per signal, in the words staff read on the row. */ export function describeSaleRiskSignals(signals) {
60
+ return signals.map((signal)=>{
61
+ switch(signal.code){
62
+ case 'young-publisher-large-sale':
63
+ return `The publisher's workspace is ${signal.ageDays} day(s) old and this one sale was ` + `$${(signal.amountCents / 100).toFixed(2)}.`;
64
+ case 'shared-member':
65
+ return `The buying and publishing workspaces share ${signal.uids.length} member(s): ` + `${signal.uids.join(', ')}.`;
66
+ case 'card-reused-across-buyers':
67
+ return 'The same card also bought from this publisher for other workspace(s): ' + `${signal.otherBuyerOrgIds.join(', ')}.`;
68
+ default:
69
+ return 'Sale risk signal.';
70
+ }
71
+ });
72
+ }
73
+
74
+ //# sourceMappingURL=publisher-risk.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/marketplace/src/lib/model/publisher-risk.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/*==========================================\n * A YOUNG PUBLISHER'S RISK TIER (AGL-3365).\n *\n * The 9/26 actor signed up, paid for a plan on a card that never met 3-D\n * Secure, and within days was sending lures. On the marketplace the same\n * actor has one more move: publish a listing, buy it with stolen cards, and\n * cash the sales out through the payouts the platform sends the publisher.\n *\n * Every marketplace sale is a destination charge — the funds settle on the\n * platform and the publisher's share moves to its connected account at the\n * charge — so the lever the platform keeps is the connected account's PAYOUT\n * SCHEDULE: how long a share sits in the account before it leaves Stripe. A\n * share still in the account can be reversed when a card turns out stolen\n * (the refund and dispute doors already reverse it); a share already paid\n * out to a bank cannot.\n *\n * So a publisher workspace in its first {@link YOUNG_PUBLISHER_DAYS} days has\n * its payouts held {@link YOUNG_PUBLISHER_PAYOUT_DELAY_DAYS} days, which\n * outlasts the window in which issuers send early fraud warnings on most\n * stolen-card charges, and a single sale of\n * {@link YOUNG_PUBLISHER_REVIEW_SALE_CENTS} or more to one files a staff\n * signal while the money can still be reversed. A publisher that ages out of\n * the tier returns to the account's standard schedule on its next sale or its\n * next visit to the payout settings.\n *\n * Age alone, as the phishing screen's tier is: payment history is what the\n * actor bought.\n *=========================================*/\n\n/** A publisher workspace younger than this many days is in the tier. */\nexport const YOUNG_PUBLISHER_DAYS = 30\n\n/** How long a young publisher's payouts sit in its account. */\nexport const YOUNG_PUBLISHER_PAYOUT_DELAY_DAYS = 14\n\n/** A young publisher's single sale at or above this files a staff signal. */\nexport const YOUNG_PUBLISHER_REVIEW_SALE_CENTS = 10_000\n\n/**\n * Is a publisher workspace of this age (days) in the young tier? `null` — a\n * creation date that cannot be read — is an existing customer, as the\n * phishing screen reads it, and is not young.\n */\nexport function isYoungPublisher(ageDays: number | null | undefined): boolean {\n return (\n typeof ageDays === 'number' &&\n Number.isFinite(ageDays) &&\n ageDays < YOUNG_PUBLISHER_DAYS\n )\n}\n\n/**\n * The payout delay a publisher's account should carry: a number of days, or\n * `null` for the account's standard (minimum) schedule.\n */\nexport function publisherPayoutDelayDays(\n ageDays: number | null | undefined,\n): number | null {\n return isYoungPublisher(ageDays) ? YOUNG_PUBLISHER_PAYOUT_DELAY_DAYS : null\n}\n\n/** One reason a sale looks like the publisher paying itself or cashing out cards. */\nexport type SaleRiskSignal =\n | {\n code: 'young-publisher-large-sale'\n ageDays: number\n amountCents: number\n }\n | {\n /** The buyer and the publisher share a member (a uid in both workspaces). */\n code: 'shared-member'\n uids: string[]\n }\n | {\n /**\n * The card that paid was used by ANOTHER buyer workspace to buy from\n * the same publisher — one card, several buyers, one seller.\n */\n code: 'card-reused-across-buyers'\n otherBuyerOrgIds: string[]\n }\n\n/** One line per signal, in the words staff read on the row. */\nexport function describeSaleRiskSignals(\n signals: readonly SaleRiskSignal[],\n): string[] {\n return signals.map((signal) => {\n switch (signal.code) {\n case 'young-publisher-large-sale':\n return (\n `The publisher's workspace is ${signal.ageDays} day(s) old and this one sale was ` +\n `$${(signal.amountCents / 100).toFixed(2)}.`\n )\n case 'shared-member':\n return (\n `The buying and publishing workspaces share ${signal.uids.length} member(s): ` +\n `${signal.uids.join(', ')}.`\n )\n case 'card-reused-across-buyers':\n return (\n 'The same card also bought from this publisher for other workspace(s): ' +\n `${signal.otherBuyerOrgIds.join(', ')}.`\n )\n default:\n return 'Sale risk signal.'\n }\n })\n}\n"],"names":["YOUNG_PUBLISHER_DAYS","YOUNG_PUBLISHER_PAYOUT_DELAY_DAYS","YOUNG_PUBLISHER_REVIEW_SALE_CENTS","isYoungPublisher","ageDays","Number","isFinite","publisherPayoutDelayDays","describeSaleRiskSignals","signals","map","signal","code","amountCents","toFixed","uids","length","join","otherBuyerOrgIds"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED;;;;;;;;;;;;;;;;;;;;;;;;;;;2CA2B2C,GAE3C,sEAAsE,GACtE,OAAO,MAAMA,uBAAuB,GAAE;AAEtC,6DAA6D,GAC7D,OAAO,MAAMC,oCAAoC,GAAE;AAEnD,2EAA2E,GAC3E,OAAO,MAAMC,oCAAoC,MAAM;AAEvD;;;;CAIC,GACD,OAAO,SAASC,iBAAiBC,OAAkC;IACjE,OACE,OAAOA,YAAY,YACnBC,OAAOC,QAAQ,CAACF,YAChBA,UAAUJ;AAEd;AAEA;;;CAGC,GACD,OAAO,SAASO,yBACdH,OAAkC;IAElC,OAAOD,iBAAiBC,WAAWH,oCAAoC;AACzE;AAuBA,6DAA6D,GAC7D,OAAO,SAASO,wBACdC,OAAkC;IAElC,OAAOA,QAAQC,GAAG,CAAC,CAACC;QAClB,OAAQA,OAAOC,IAAI;YACjB,KAAK;gBACH,OACE,CAAC,6BAA6B,EAAED,OAAOP,OAAO,CAAC,kCAAkC,CAAC,GAClF,CAAC,CAAC,EAAE,AAACO,CAAAA,OAAOE,WAAW,GAAG,GAAE,EAAGC,OAAO,CAAC,GAAG,CAAC,CAAC;YAEhD,KAAK;gBACH,OACE,CAAC,2CAA2C,EAAEH,OAAOI,IAAI,CAACC,MAAM,CAAC,YAAY,CAAC,GAC9E,GAAGL,OAAOI,IAAI,CAACE,IAAI,CAAC,MAAM,CAAC,CAAC;YAEhC,KAAK;gBACH,OACE,2EACA,GAAGN,OAAOO,gBAAgB,CAACD,IAAI,CAAC,MAAM,CAAC,CAAC;YAE5C;gBACE,OAAO;QACX;IACF;AACF"}
@@ -23,7 +23,10 @@ import { _ as _extends } from "@swc/helpers/_/_extends";
23
23
  // the surviving half of "four server events ship to nothing" now that the
24
24
  // Measurement Protocol credentials have landed (2026-08-17).
25
25
  import { after } from "next/server";
26
- import { firebaseAdmin, sendGa4Purchase, sendGa4Refund, clearConnectPayoutFailure, recordConnectPayoutFailure, syncConnectAccountStatus } from "@aglyn/tenant-data-admin";
26
+ import { paymentRiskEventFrom } from "@aglyn/aglyn/app-utils/payment-risk";
27
+ import { recordPaymentRiskOnRecord } from "@aglyn/tenant-data-admin/server/payment-risk-record";
28
+ import { firebaseAdmin, notifyStaff, sendGa4Purchase, sendGa4Refund, clearConnectPayoutFailure, recordConnectPayoutFailure, syncConnectAccountStatus } from "@aglyn/tenant-data-admin";
29
+ import { screenMarketplaceSale } from "./sale-risk.js";
27
30
  /** Stripe failures a redelivery can actually fix. */ function isTransientStripeStatus(status) {
28
31
  return status === 429 || status >= 500;
29
32
  }
@@ -707,6 +710,43 @@ async function reverseMarketplaceSellerShare(purchaseRef, cause) {
707
710
  // Recorded and surfaced, never retried: Stripe runs its own retry schedule
708
711
  // and a second transfer against an account that just refused one is how a
709
712
  // duplicate lands.
713
+ // A CARD FRAUD SIGNAL ON A MARKETPLACE SALE (AGL-3365).
714
+ //
715
+ // An issuer's early fraud warning or a Radar review, landing on a charge
716
+ // that paid a publisher. The purchase carries the signal (the same
717
+ // `paymentRisk` field an order or booking carries), and the claim names the
718
+ // PUBLISHER's workspace, so the platform route counts it on the seller's
719
+ // ledger against the org that was paid — never the buyer's workspace,
720
+ // whose card it may have been. Nobody is notified per signal: the
721
+ // publisher cannot refund a charge the platform is merchant of record for,
722
+ // and staff hear through the seller pattern and the sale-risk row.
723
+ // Disputes keep their own section below, which claims them the same way.
724
+ if (type === 'radar.early_fraud_warning.created' || type === 'review.opened') {
725
+ var _purchase_get;
726
+ const risk = paymentRiskEventFrom(type, object);
727
+ if (!(risk == null ? void 0 : risk.paymentIntentId)) return;
728
+ const firestore = firebaseAdmin.app().firestore();
729
+ const purchases = await firestore.collection('marketplacePurchases').where('paymentIntentId', '==', risk.paymentIntentId).limit(1).get();
730
+ if (purchases.empty) return;
731
+ const purchase = purchases.docs[0];
732
+ await recordPaymentRiskOnRecord({
733
+ ref: purchase.ref,
734
+ signal: risk.signal,
735
+ hostId: '',
736
+ subjectLabel: 'Marketplace sale',
737
+ link: '/admin/abuse-reports',
738
+ notificationType: 'marketplace.review'
739
+ }, {
740
+ firestore,
741
+ notify: async ()=>undefined
742
+ });
743
+ const sellerOrgId = String((_purchase_get = purchase.get('sellerOrgId')) != null ? _purchase_get : '');
744
+ return _extends({
745
+ claimed: true
746
+ }, sellerOrgId ? {
747
+ orgId: sellerOrgId
748
+ } : {});
749
+ }
710
750
  if (type === 'payout.failed' || type === 'transfer.failed') {
711
751
  var _ref1, _ref2, _ref3;
712
752
  var _object_destination;
@@ -879,6 +919,29 @@ async function reverseMarketplaceSellerShare(purchaseRef, cause) {
879
919
  stripeCustomerId: String((_ref1 = object == null ? void 0 : object.customer) != null ? _ref1 : '') || String(buyerUid)
880
920
  }).catch(()=>undefined);
881
921
  });
922
+ // The sale's fraud shape (AGL-3365): the publisher paying itself, one
923
+ // card buying for several workspaces, a brand-new publisher's large
924
+ // sale — and a young publisher's payouts held. Once per sale, after the
925
+ // response like the GA hit: it reads and files, and the sale it reads
926
+ // is already recorded. `screenMarketplaceSale` never throws.
927
+ if (!alreadyRecorded) {
928
+ after(()=>{
929
+ var _ref;
930
+ return screenMarketplaceSale({
931
+ purchaseRef,
932
+ sessionId: String(object.id),
933
+ buyerUid: String(buyerUid),
934
+ buyerOrgId: buyerOrgId ? String(buyerOrgId) : null,
935
+ sellerOrgId: String(sellerOrgId),
936
+ amountCents: grossCents - taxCents,
937
+ paymentIntentId: String((_ref = object == null ? void 0 : object.payment_intent) != null ? _ref : ''),
938
+ livemode: (event == null ? void 0 : event.livemode) === true
939
+ }, {
940
+ stripeKey: process.env.STRIPE_SECRET_KEY,
941
+ notify: notifyStaff
942
+ }).catch(()=>undefined);
943
+ });
944
+ }
882
945
  // A refund or a lost dispute that arrived before this document existed
883
946
  // is applied NOW (AGL-2148) — revocation, GA and the transfer reversal,
884
947
  // by the same function the refund door uses. Last on this path, and
@@ -1008,14 +1071,17 @@ async function reverseMarketplaceSellerShare(purchaseRef, cause) {
1008
1071
  // purpose: a dispute carrying no payment intent cannot be joined to a
1009
1072
  // purchase, which is a fault, not a routine miss.
1010
1073
  let claimed = false;
1074
+ // The publisher the disputed sale paid (AGL-3365), for the seller ledger.
1075
+ let sellerOrgId = '';
1011
1076
  if (disputeId && paymentIntentId) {
1012
1077
  var _ref24;
1013
1078
  const firestore = firebaseAdmin.app().firestore();
1014
1079
  const purchases = await firestore.collection('marketplacePurchases').where('paymentIntentId', '==', paymentIntentId).limit(1).get();
1015
1080
  if (!purchases.empty) {
1016
- var _ref25;
1081
+ var _purchase_get1, _ref25;
1017
1082
  claimed = true;
1018
1083
  const purchase = purchases.docs[0];
1084
+ sellerOrgId = String((_purchase_get1 = purchase.get('sellerOrgId')) != null ? _purchase_get1 : '');
1019
1085
  const status = String((_ref25 = object == null ? void 0 : object.status) != null ? _ref25 : '');
1020
1086
  if (type === 'charge.dispute.created') {
1021
1087
  await purchase.ref.set({
@@ -1065,9 +1131,11 @@ async function reverseMarketplaceSellerShare(purchaseRef, cause) {
1065
1131
  claimed = await recordDisputeOrphanIfMarketplace(firestore, object, paymentIntentId);
1066
1132
  }
1067
1133
  }
1068
- return {
1134
+ return _extends({
1069
1135
  claimed
1070
- };
1136
+ }, claimed && sellerOrgId ? {
1137
+ orgId: sellerOrgId
1138
+ } : {});
1071
1139
  }
1072
1140
  };
1073
1141