@alexasomba/better-auth-paystack 2.4.4 → 2.5.0

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 (35) hide show
  1. package/README.md +42 -5
  2. package/dist/client.d.mts +1 -1
  3. package/dist/client.mjs +1 -1
  4. package/dist/index.d.mts +65 -3
  5. package/dist/index.d.mts.map +1 -1
  6. package/dist/index.mjs +338 -213
  7. package/dist/index.mjs.map +1 -1
  8. package/dist/{types-DHZSS1K6.d.mts → types-D-iIbe9E.d.mts} +9 -2
  9. package/dist/types-D-iIbe9E.d.mts.map +1 -0
  10. package/dist/version-DRyh6Dnz.mjs +6 -0
  11. package/dist/version-DRyh6Dnz.mjs.map +1 -0
  12. package/package.json +40 -36
  13. package/skills/{setup → better-auth-paystack-setup}/SKILL.md +2 -2
  14. package/skills/better-auth-paystack-setup/agents/openai.yaml +6 -0
  15. package/skills/{subscriptions-and-transactions → paystack-billing-flows}/SKILL.md +11 -3
  16. package/skills/paystack-billing-flows/agents/openai.yaml +6 -0
  17. package/skills/{billing-catalog-and-limits → paystack-catalog-limits}/SKILL.md +2 -2
  18. package/skills/paystack-catalog-limits/agents/openai.yaml +6 -0
  19. package/skills/{client-api-contract → paystack-client-api}/SKILL.md +2 -2
  20. package/skills/paystack-client-api/agents/openai.yaml +6 -0
  21. package/skills/{local-subscription-lifecycle → paystack-local-subscriptions}/SKILL.md +2 -2
  22. package/skills/paystack-local-subscriptions/agents/openai.yaml +6 -0
  23. package/skills/{organization-billing → paystack-organization-billing}/SKILL.md +25 -4
  24. package/skills/paystack-organization-billing/agents/openai.yaml +6 -0
  25. package/skills/{schema-and-migrations → paystack-schema-migrations}/SKILL.md +2 -2
  26. package/skills/paystack-schema-migrations/agents/openai.yaml +6 -0
  27. package/skills/{tanstack-start → paystack-tanstack-start}/SKILL.md +2 -2
  28. package/skills/paystack-tanstack-start/agents/openai.yaml +6 -0
  29. package/skills/{testing-and-fixtures → paystack-testing-fixtures}/SKILL.md +2 -2
  30. package/skills/paystack-testing-fixtures/agents/openai.yaml +6 -0
  31. package/skills/{webhooks-and-event-processing → paystack-webhooks-events}/SKILL.md +17 -3
  32. package/skills/paystack-webhooks-events/agents/openai.yaml +6 -0
  33. package/dist/types-DHZSS1K6.d.mts.map +0 -1
  34. package/dist/version-LjwMcXaE.mjs +0 -6
  35. package/dist/version-LjwMcXaE.mjs.map +0 -1
package/dist/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { t as PACKAGE_VERSION } from "./version-LjwMcXaE.mjs";
1
+ import { t as PACKAGE_VERSION } from "./version-DRyh6Dnz.mjs";
2
2
  import { HIDE_METADATA, defineErrorCodes } from "better-auth";
3
3
  import { defu } from "defu";
4
4
  import { APIError, createAuthEndpoint, createAuthMiddleware, getSessionFromCtx, originCheck, sessionMiddleware } from "better-auth/api";
@@ -425,11 +425,18 @@ async function syncSubscriptionSeats(ctx, organizationId, options) {
425
425
  }
426
426
  //#endregion
427
427
  //#region src/reference-access.ts
428
- const BILLING_ORG_ROLES = new Set(["owner", "admin"]);
429
- function hasBillingRole(role) {
430
- if (Array.isArray(role)) return role.some((value) => hasBillingRole(value));
428
+ const DEFAULT_BILLING_ORG_ROLES = ["owner", "admin"];
429
+ function normalizeBillingRoles(roles) {
430
+ return new Set(roles.map((value) => value.trim()).filter((value) => value !== ""));
431
+ }
432
+ function getBillingRoles(options) {
433
+ return options.organization?.billingRoles ?? DEFAULT_BILLING_ORG_ROLES;
434
+ }
435
+ function hasBillingRole(role, billingRoles = DEFAULT_BILLING_ORG_ROLES) {
436
+ const allowedRoles = normalizeBillingRoles(billingRoles);
437
+ if (Array.isArray(role)) return role.some((value) => hasBillingRole(value, billingRoles));
431
438
  if (typeof role !== "string") return false;
432
- return role.split(",").map((value) => value.trim()).some((value) => BILLING_ORG_ROLES.has(value));
439
+ return role.split(",").map((value) => value.trim()).some((value) => allowedRoles.has(value));
433
440
  }
434
441
  function resolveBillingReferenceId(input) {
435
442
  const body = input.body ?? {};
@@ -459,7 +466,7 @@ async function authorizeBillingReference(ctx, options, data) {
459
466
  value: data.referenceId
460
467
  }]
461
468
  });
462
- if (member !== null && member !== void 0 && hasBillingRole(member.role)) return;
469
+ if (member !== null && member !== void 0 && hasBillingRole(member.role, getBillingRoles(options))) return;
463
470
  }
464
471
  throw new APIError("UNAUTHORIZED");
465
472
  }
@@ -617,6 +624,311 @@ async function handleProratedUpgrade(ctx, options, input) {
617
624
  };
618
625
  }
619
626
  //#endregion
627
+ //#region src/reconciliation.ts
628
+ function getAllowedSubscriptionChannels$1(options) {
629
+ const channels = options.subscription?.allowedPaymentChannels;
630
+ return Array.isArray(channels) && channels.length > 0 ? channels : void 0;
631
+ }
632
+ function isAllowedSubscriptionChannel(channel, allowedChannels) {
633
+ if (allowedChannels === void 0) return true;
634
+ return channel !== void 0 && channel !== null && allowedChannels.includes(channel);
635
+ }
636
+ function createSummary() {
637
+ return {
638
+ transaction: {
639
+ found: false,
640
+ updated: false
641
+ },
642
+ subscription: {
643
+ found: false,
644
+ updated: false,
645
+ prorationApplied: false
646
+ },
647
+ customer: { saved: false },
648
+ product: { synced: false }
649
+ };
650
+ }
651
+ function getErrorMessage(error, fallback) {
652
+ return error instanceof Error && error.message !== "" ? error.message : fallback;
653
+ }
654
+ function createFailureResult(input) {
655
+ return {
656
+ ok: false,
657
+ source: input.source,
658
+ status: input.status,
659
+ reference: input.reference,
660
+ data: input.data,
661
+ error: input.error,
662
+ ...input.summary
663
+ };
664
+ }
665
+ function throwOrReturnFailure(input) {
666
+ if (input.throwOnError) throw new APIError(input.apiStatus, {
667
+ code: input.error.code,
668
+ message: input.error.message,
669
+ status: input.error.status
670
+ });
671
+ return createFailureResult(input);
672
+ }
673
+ function parseMetadata(metadata) {
674
+ if (metadata === void 0 || metadata === null || metadata === "") return {};
675
+ if (typeof metadata === "string") try {
676
+ return JSON.parse(metadata);
677
+ } catch {
678
+ return {};
679
+ }
680
+ if (typeof metadata === "object") return metadata;
681
+ return {};
682
+ }
683
+ function hasReferenceMismatch(input) {
684
+ return input.expectedReferenceId !== void 0 && input.expectedReferenceId !== "" && input.transactionReferenceId !== void 0 && input.transactionReferenceId !== null && input.transactionReferenceId !== "" && input.expectedReferenceId !== input.transactionReferenceId;
685
+ }
686
+ function getNonEmptyString(value) {
687
+ return typeof value === "string" && value !== "" ? value : void 0;
688
+ }
689
+ async function reconcilePaystackTransaction(ctx, options, input) {
690
+ const source = input.source ?? "server";
691
+ const throwOnError = input.throwOnError === true;
692
+ const summary = createSummary();
693
+ const paystack = getPaystackOps(options.paystackClient);
694
+ let data;
695
+ try {
696
+ data = unwrapSdkResult(await paystack?.transaction?.verify(input.reference));
697
+ } catch (error) {
698
+ ctx.context.logger.error("Failed to verify Paystack transaction", error);
699
+ return throwOrReturnFailure({
700
+ throwOnError,
701
+ apiStatus: "BAD_REQUEST",
702
+ source,
703
+ status: "error",
704
+ reference: input.reference,
705
+ data: null,
706
+ summary,
707
+ error: {
708
+ code: "FAILED_TO_VERIFY_TRANSACTION",
709
+ message: getErrorMessage(error, "Failed to verify transaction"),
710
+ status: 400
711
+ }
712
+ });
713
+ }
714
+ if (data === void 0 || data === null) return throwOrReturnFailure({
715
+ throwOnError,
716
+ apiStatus: "BAD_REQUEST",
717
+ source,
718
+ status: "error",
719
+ reference: input.reference,
720
+ data: null,
721
+ summary,
722
+ error: {
723
+ code: "FAILED_TO_VERIFY_TRANSACTION",
724
+ message: "Failed to fetch transaction data from Paystack.",
725
+ status: 400
726
+ }
727
+ });
728
+ const status = data.status ?? "failed";
729
+ const reference = data.reference ?? input.reference;
730
+ const paystackIdRaw = data.id;
731
+ const paystackId = paystackIdRaw !== void 0 && paystackIdRaw !== null ? String(paystackIdRaw) : void 0;
732
+ const authorizationCode = data.authorization?.authorization_code;
733
+ const store = createBillingStore(ctx);
734
+ const txRecord = await store.findTransactionByReference(reference);
735
+ summary.transaction.found = txRecord !== null;
736
+ summary.transaction.previousStatus = txRecord?.status;
737
+ if (hasReferenceMismatch({
738
+ expectedReferenceId: input.referenceId,
739
+ transactionReferenceId: txRecord?.referenceId
740
+ })) return throwOrReturnFailure({
741
+ throwOnError,
742
+ apiStatus: "UNAUTHORIZED",
743
+ source,
744
+ status,
745
+ reference,
746
+ data,
747
+ summary,
748
+ error: {
749
+ code: "REFERENCE_ID_MISMATCH",
750
+ message: "Transaction reference does not belong to the expected billing reference.",
751
+ status: 401
752
+ }
753
+ });
754
+ const referenceId = input.referenceId ?? getNonEmptyString(txRecord?.referenceId) ?? getNonEmptyString(input.actor?.user.id);
755
+ summary.transaction.referenceId = referenceId;
756
+ if (input.actor !== void 0 && referenceId !== void 0 && referenceId !== input.actor.user.id) try {
757
+ await authorizeBillingReference(ctx, options, {
758
+ user: input.actor.user,
759
+ session: input.actor.session,
760
+ referenceId,
761
+ action: "verify-transaction"
762
+ });
763
+ } catch (error) {
764
+ return throwOrReturnFailure({
765
+ throwOnError,
766
+ apiStatus: "UNAUTHORIZED",
767
+ source,
768
+ status,
769
+ reference,
770
+ data,
771
+ summary,
772
+ error: {
773
+ code: "UNAUTHORIZED",
774
+ message: getErrorMessage(error, "Not authorized to reconcile this transaction."),
775
+ status: 401
776
+ }
777
+ });
778
+ }
779
+ const transactionUpdate = {
780
+ status,
781
+ paystackId,
782
+ amount: data.amount,
783
+ currency: data.currency,
784
+ updatedAt: /* @__PURE__ */ new Date()
785
+ };
786
+ const updatedTransaction = await store.updateTransactionByReference(reference, transactionUpdate);
787
+ summary.transaction.updated = updatedTransaction !== null;
788
+ summary.transaction.status = status;
789
+ if (status !== "success") return {
790
+ ok: true,
791
+ source,
792
+ status,
793
+ reference,
794
+ data,
795
+ ...summary
796
+ };
797
+ const allowedSubscriptionChannels = getAllowedSubscriptionChannels$1(options);
798
+ if ((txRecord?.plan !== void 0 && txRecord.plan !== null && txRecord.plan !== "" || Boolean(data.plan)) && isAllowedSubscriptionChannel(data.channel ?? void 0, allowedSubscriptionChannels) === false) {
799
+ await store.updateTransactionByReference(reference, {
800
+ ...transactionUpdate,
801
+ status: "failed"
802
+ });
803
+ summary.transaction.updated = true;
804
+ summary.transaction.status = "failed";
805
+ return throwOrReturnFailure({
806
+ throwOnError,
807
+ apiStatus: "BAD_REQUEST",
808
+ source,
809
+ status: "failed",
810
+ reference,
811
+ data,
812
+ summary,
813
+ error: {
814
+ code: "SUBSCRIPTION_PAYMENT_CHANNEL_NOT_ALLOWED",
815
+ message: `This subscription requires one of: ${allowedSubscriptionChannels?.join(", ") ?? "allowed channels"}.`,
816
+ status: 400
817
+ }
818
+ });
819
+ }
820
+ const paystackCustomerCodeFromPaystack = data.customer?.customer_code;
821
+ if (paystackCustomerCodeFromPaystack !== void 0 && paystackCustomerCodeFromPaystack !== null && paystackCustomerCodeFromPaystack !== "" && referenceId !== void 0 && referenceId !== "") {
822
+ let isOrganization = options.organization?.enabled === true && typeof referenceId === "string" && referenceId.startsWith("org_");
823
+ if (isOrganization === false && options.organization?.enabled === true) isOrganization = await store.findOrganization(referenceId) !== null;
824
+ await store.saveCustomerCode(referenceId, paystackCustomerCodeFromPaystack, isOrganization);
825
+ summary.customer.saved = true;
826
+ summary.customer.referenceId = referenceId;
827
+ summary.customer.customerCode = paystackCustomerCodeFromPaystack;
828
+ summary.customer.model = isOrganization ? "organization" : "user";
829
+ }
830
+ const transaction = updatedTransaction ?? await store.findTransactionByReference(reference);
831
+ if (transaction !== void 0 && transaction !== null && transaction.product !== void 0 && transaction.product !== null && transaction.product !== "" && options.paystackClient !== void 0 && options.paystackClient !== null) {
832
+ await syncProductQuantityFromPaystack(ctx, transaction.product, options.paystackClient);
833
+ summary.product.synced = true;
834
+ summary.product.name = transaction.product;
835
+ }
836
+ if (options.subscription?.enabled !== true) return {
837
+ ok: true,
838
+ source,
839
+ status,
840
+ reference,
841
+ data,
842
+ ...summary
843
+ };
844
+ const metadataObj = parseMetadata(data.metadata);
845
+ const isTrial = metadataObj.isTrial === true || metadataObj.isTrial === "true";
846
+ const trialEnd = metadataObj.trialEnd;
847
+ const targetPlan = metadataObj.plan;
848
+ if (metadataObj.type === "proration") {
849
+ const subscriptionId = metadataObj.subscriptionId;
850
+ const newPlan = metadataObj.newPlan;
851
+ const newSeatCount = metadataObj.newSeatCount;
852
+ if (subscriptionId !== void 0 && subscriptionId !== "" && newPlan !== void 0 && newPlan !== "") {
853
+ const updatedSubscription = await store.updateSubscription(subscriptionId, {
854
+ plan: newPlan,
855
+ ...typeof newSeatCount === "number" ? { seats: newSeatCount } : {},
856
+ paystackTransactionReference: reference,
857
+ ...authorizationCode !== void 0 && authorizationCode !== null ? { paystackAuthorizationCode: authorizationCode } : {},
858
+ updatedAt: /* @__PURE__ */ new Date()
859
+ });
860
+ summary.subscription.found = updatedSubscription !== null;
861
+ summary.subscription.updated = updatedSubscription !== null;
862
+ summary.subscription.id = updatedSubscription?.id ?? subscriptionId;
863
+ summary.subscription.status = updatedSubscription?.status;
864
+ summary.subscription.prorationApplied = updatedSubscription !== null;
865
+ }
866
+ return {
867
+ ok: true,
868
+ source,
869
+ status,
870
+ reference,
871
+ data,
872
+ ...summary
873
+ };
874
+ }
875
+ let paystackSubscriptionCode;
876
+ const targetSub = (await store.findSubscriptionsByTransactionReference(reference)).find((subscription) => referenceId === void 0 || referenceId === "" || subscription.referenceId === referenceId);
877
+ summary.subscription.found = targetSub !== void 0;
878
+ summary.subscription.id = targetSub?.id;
879
+ summary.subscription.status = targetSub?.status;
880
+ if (isTrial && targetPlan !== void 0 && trialEnd !== void 0) {
881
+ const email = data.customer?.email;
882
+ const planConfig = (await getPlans(options.subscription)).find((plan) => plan.name.toLowerCase() === targetPlan.toLowerCase());
883
+ if (planConfig !== void 0 && planConfig !== null && (planConfig.planCode === void 0 || planConfig.planCode === null || planConfig.planCode === "")) paystackSubscriptionCode = `LOC_${reference}`;
884
+ else if (targetSub?.paystackSubscriptionCode !== void 0 && targetSub.paystackSubscriptionCode !== null && targetSub.paystackSubscriptionCode !== "") paystackSubscriptionCode = targetSub.paystackSubscriptionCode;
885
+ else if (authorizationCode !== void 0 && authorizationCode !== null && email !== void 0 && email !== null && email !== "" && planConfig?.planCode !== void 0 && planConfig.planCode !== null && planConfig.planCode !== "") paystackSubscriptionCode = unwrapSdkResult(await paystack?.subscription?.create({ body: {
886
+ customer: email,
887
+ plan: planConfig.planCode,
888
+ authorization: authorizationCode,
889
+ start_date: trialEnd
890
+ } }))?.subscription_code;
891
+ } else if (isTrial === false) {
892
+ const planCodeFromPaystack = data.plan?.plan_code;
893
+ if (planCodeFromPaystack === void 0 || planCodeFromPaystack === null || planCodeFromPaystack === "") paystackSubscriptionCode = `LOC_${reference}`;
894
+ else paystackSubscriptionCode = data.subscription?.subscription_code ?? void 0;
895
+ }
896
+ let updatedSubscription = null;
897
+ if (targetSub !== void 0 && targetSub !== null) {
898
+ updatedSubscription = await store.updateSubscription(targetSub.id, {
899
+ status: isTrial ? "trialing" : "active",
900
+ periodStart: /* @__PURE__ */ new Date(),
901
+ updatedAt: /* @__PURE__ */ new Date(),
902
+ ...isTrial && trialEnd !== void 0 ? {
903
+ trialStart: /* @__PURE__ */ new Date(),
904
+ trialEnd: new Date(trialEnd),
905
+ periodEnd: new Date(trialEnd)
906
+ } : {},
907
+ ...paystackSubscriptionCode !== void 0 ? { paystackSubscriptionCode } : {},
908
+ ...authorizationCode !== void 0 && authorizationCode !== null ? { paystackAuthorizationCode: authorizationCode } : {}
909
+ });
910
+ summary.subscription.updated = updatedSubscription !== null;
911
+ summary.subscription.id = updatedSubscription?.id ?? targetSub.id;
912
+ summary.subscription.status = updatedSubscription?.status ?? targetSub.status;
913
+ }
914
+ if (updatedSubscription !== void 0 && updatedSubscription !== null && options.subscription?.onSubscriptionComplete !== void 0) {
915
+ const plan = (await getPlans(options.subscription)).find((candidate) => candidate.name.toLowerCase() === updatedSubscription.plan.toLowerCase());
916
+ if (plan !== void 0) await options.subscription.onSubscriptionComplete({
917
+ event: data,
918
+ subscription: updatedSubscription,
919
+ plan
920
+ }, ctx);
921
+ }
922
+ return {
923
+ ok: true,
924
+ source,
925
+ status,
926
+ reference,
927
+ data,
928
+ ...summary
929
+ };
930
+ }
931
+ //#endregion
620
932
  //#region src/routes.ts
621
933
  const PAYSTACK_ERROR_CODES = defineErrorCodes({
622
934
  SUBSCRIPTION_NOT_FOUND: "Subscription not found",
@@ -633,10 +945,6 @@ function getAllowedSubscriptionChannels(options) {
633
945
  const channels = options.subscription?.allowedPaymentChannels;
634
946
  return Array.isArray(channels) && channels.length > 0 ? channels : void 0;
635
947
  }
636
- function isAllowedSubscriptionChannel(channel, allowedChannels) {
637
- if (allowedChannels === void 0) return true;
638
- return channel !== void 0 && channel !== null && allowedChannels.includes(channel);
639
- }
640
948
  async function hmacSha512Hex(secret, message) {
641
949
  const encoder = new TextEncoder();
642
950
  const keyData = encoder.encode(secret);
@@ -1248,217 +1556,34 @@ const upgradeSubscription = (options, path = "/upgrade-subscription") => initial
1248
1556
  const cancelSubscription = (options, path = "/cancel-subscription") => disablePaystackSubscription(options, path);
1249
1557
  const restoreSubscription = (options, path = "/restore-subscription") => enablePaystackSubscription(options, path);
1250
1558
  const verifyTransaction = (options, path = "/verify-transaction") => {
1251
- const verifyBodySchema = z.object({ reference: z.string() });
1252
- const subscriptionOptions = options.subscription;
1253
1559
  return createAuthEndpoint(path, {
1254
1560
  method: "POST",
1255
- body: verifyBodySchema,
1256
- use: subscriptionOptions?.enabled === true ? [
1561
+ body: z.object({ reference: z.string() }),
1562
+ use: options.subscription?.enabled === true ? [
1257
1563
  sessionMiddleware,
1258
1564
  originCheck,
1259
1565
  referenceMiddleware(options, "verify-transaction")
1260
1566
  ] : [sessionMiddleware, originCheck]
1261
1567
  }, async (ctx) => {
1262
- const paystack = getPaystackOps(options.paystackClient);
1263
- let data;
1264
- try {
1265
- data = unwrapSdkResult(await paystack?.transaction?.verify(ctx.body.reference));
1266
- } catch (error) {
1267
- ctx.context.logger.error("Failed to verify Paystack transaction", error);
1268
- throw new APIError("BAD_REQUEST", {
1269
- code: "FAILED_TO_VERIFY_TRANSACTION",
1270
- message: error instanceof Error ? error.message : PAYSTACK_ERROR_CODES.FAILED_TO_VERIFY_TRANSACTION.message
1271
- });
1272
- }
1273
- if (data === void 0 || data === null) throw new APIError("BAD_REQUEST", { message: "Failed to fetch transaction data from Paystack." });
1274
- const status = data.status ?? "failed";
1275
- const reference = data.reference ?? ctx.body.reference;
1276
- const paystackIdRaw = data.id;
1277
- const paystackId = paystackIdRaw !== void 0 && paystackIdRaw !== null ? String(paystackIdRaw) : void 0;
1278
- const authorizationCode = data.authorization?.authorization_code;
1279
- const allowedSubscriptionChannels = getAllowedSubscriptionChannels(options);
1280
- if (status === "success") {
1281
- const session = await getSessionFromCtx(ctx);
1282
- const txRecord = await ctx.context.adapter.findOne({
1283
- model: "paystackTransaction",
1284
- where: [{
1285
- field: "reference",
1286
- value: reference
1287
- }]
1288
- });
1289
- const referenceId = txRecord !== void 0 && txRecord !== null && txRecord.referenceId !== void 0 && txRecord.referenceId !== null && txRecord.referenceId !== "" ? txRecord.referenceId : session !== void 0 && session !== null ? session.user.id : void 0;
1290
- if ((txRecord?.plan !== void 0 && txRecord.plan !== null && txRecord.plan !== "" || Boolean(data.plan)) && isAllowedSubscriptionChannel(data.channel ?? void 0, allowedSubscriptionChannels) === false) {
1291
- await ctx.context.adapter.update({
1292
- model: "paystackTransaction",
1293
- update: {
1294
- status: "failed",
1295
- paystackId,
1296
- amount: data.amount,
1297
- currency: data.currency,
1298
- updatedAt: /* @__PURE__ */ new Date()
1299
- },
1300
- where: [{
1301
- field: "reference",
1302
- value: reference
1303
- }]
1304
- });
1305
- throw new APIError("BAD_REQUEST", {
1306
- code: "SUBSCRIPTION_PAYMENT_CHANNEL_NOT_ALLOWED",
1307
- message: `This subscription requires one of: ${allowedSubscriptionChannels?.join(", ") ?? "allowed channels"}.`
1308
- });
1309
- }
1310
- if (session !== void 0 && session !== null && referenceId !== void 0 && referenceId !== null && referenceId !== "" && referenceId !== session.user.id) await authorizeBillingReference(ctx, options, {
1568
+ const session = await getSessionFromCtx(ctx);
1569
+ const result = await reconcilePaystackTransaction(ctx, options, {
1570
+ reference: ctx.body.reference,
1571
+ source: "browser",
1572
+ actor: session !== void 0 && session !== null ? {
1311
1573
  user: session.user,
1312
- session: session.session,
1313
- referenceId,
1314
- action: "verify-transaction"
1315
- });
1316
- try {
1317
- await ctx.context.adapter.update({
1318
- model: "paystackTransaction",
1319
- update: {
1320
- status: "success",
1321
- paystackId,
1322
- amount: data.amount,
1323
- currency: data.currency,
1324
- updatedAt: /* @__PURE__ */ new Date()
1325
- },
1326
- where: [{
1327
- field: "reference",
1328
- value: reference
1329
- }]
1330
- });
1331
- const paystackCustomerCodeFromPaystack = data.customer?.customer_code;
1332
- if (paystackCustomerCodeFromPaystack !== void 0 && paystackCustomerCodeFromPaystack !== null && paystackCustomerCodeFromPaystack !== "" && referenceId !== void 0 && referenceId !== null && referenceId !== "") {
1333
- let isOrg = options.organization?.enabled === true && typeof referenceId === "string" && referenceId.startsWith("org_");
1334
- if (isOrg === false && options.organization?.enabled === true) {
1335
- const org = await ctx.context.adapter.findOne({
1336
- model: "organization",
1337
- where: [{
1338
- field: "id",
1339
- value: referenceId
1340
- }]
1341
- });
1342
- isOrg = org !== void 0 && org !== null;
1343
- }
1344
- if (isOrg) await ctx.context.adapter.update({
1345
- model: "organization",
1346
- update: { paystackCustomerCode: paystackCustomerCodeFromPaystack },
1347
- where: [{
1348
- field: "id",
1349
- value: referenceId
1350
- }]
1351
- });
1352
- else await ctx.context.adapter.update({
1353
- model: "user",
1354
- update: { paystackCustomerCode: paystackCustomerCodeFromPaystack },
1355
- where: [{
1356
- field: "id",
1357
- value: referenceId
1358
- }]
1359
- });
1360
- }
1361
- const transaction = await ctx.context.adapter.findOne({
1362
- model: "paystackTransaction",
1363
- where: [{
1364
- field: "reference",
1365
- value: reference
1366
- }]
1367
- });
1368
- if (transaction !== void 0 && transaction !== null && transaction.product !== void 0 && transaction.product !== null && transaction.product !== "" && options.paystackClient !== void 0 && options.paystackClient !== null) await syncProductQuantityFromPaystack(ctx, transaction.product, options.paystackClient);
1369
- let isTrial = false;
1370
- let trialEnd;
1371
- let targetPlan;
1372
- let metadataObj = {};
1373
- if (data.metadata !== void 0 && data.metadata !== null && data.metadata !== "") {
1374
- metadataObj = typeof data.metadata === "string" ? JSON.parse(data.metadata) : data.metadata;
1375
- isTrial = metadataObj.isTrial === true || metadataObj.isTrial === "true";
1376
- trialEnd = metadataObj.trialEnd;
1377
- targetPlan = metadataObj.plan;
1378
- }
1379
- if (metadataObj.type === "proration") {
1380
- const subscriptionId = metadataObj.subscriptionId;
1381
- const newPlan = metadataObj.newPlan;
1382
- const newSeatCount = metadataObj.newSeatCount;
1383
- if (subscriptionId !== void 0 && subscriptionId !== "" && newPlan !== void 0 && newPlan !== "") await ctx.context.adapter.update({
1384
- model: "subscription",
1385
- update: {
1386
- plan: newPlan,
1387
- ...typeof newSeatCount === "number" ? { seats: newSeatCount } : {},
1388
- paystackTransactionReference: reference,
1389
- ...authorizationCode !== void 0 && authorizationCode !== null ? { paystackAuthorizationCode: authorizationCode } : {},
1390
- updatedAt: /* @__PURE__ */ new Date()
1391
- },
1392
- where: [{
1393
- field: "id",
1394
- value: subscriptionId
1395
- }]
1396
- });
1397
- return ctx.json({
1398
- status,
1399
- reference,
1400
- data
1401
- });
1402
- }
1403
- let paystackSubscriptionCode;
1404
- if (isTrial && targetPlan !== void 0 && trialEnd !== void 0) {
1405
- const email = data.customer?.email;
1406
- const planConfig = (await getPlans(subscriptionOptions)).find((p) => p.name.toLowerCase() === targetPlan?.toLowerCase());
1407
- if (planConfig !== void 0 && planConfig !== null && (planConfig.planCode === void 0 || planConfig.planCode === null || planConfig.planCode === "")) paystackSubscriptionCode = `LOC_${reference}`;
1408
- if (authorizationCode !== void 0 && authorizationCode !== null && email !== void 0 && email !== null && email !== "" && planConfig?.planCode !== void 0 && planConfig.planCode !== null && planConfig.planCode !== "") paystackSubscriptionCode = unwrapSdkResult(await paystack?.subscription?.create({ body: {
1409
- customer: email,
1410
- plan: planConfig.planCode,
1411
- authorization: authorizationCode,
1412
- start_date: trialEnd
1413
- } }))?.subscription_code;
1414
- } else if (isTrial === false) {
1415
- const planCodeFromPaystack = data.plan?.plan_code;
1416
- if (planCodeFromPaystack === void 0 || planCodeFromPaystack === null || planCodeFromPaystack === "") paystackSubscriptionCode = `LOC_${reference}`;
1417
- else paystackSubscriptionCode = data.subscription?.subscription_code ?? void 0;
1418
- }
1419
- const targetSub = (await ctx.context.adapter.findMany({
1420
- model: "subscription",
1421
- where: [{
1422
- field: "paystackTransactionReference",
1423
- value: reference
1424
- }]
1425
- }))?.find((s) => referenceId === void 0 || referenceId === null || referenceId === "" || s.referenceId === referenceId);
1426
- let updatedSubscription = null;
1427
- if (targetSub !== void 0 && targetSub !== null) updatedSubscription = await ctx.context.adapter.update({
1428
- model: "subscription",
1429
- update: {
1430
- status: isTrial ? "trialing" : "active",
1431
- periodStart: /* @__PURE__ */ new Date(),
1432
- updatedAt: /* @__PURE__ */ new Date(),
1433
- ...isTrial && trialEnd !== void 0 ? {
1434
- trialStart: /* @__PURE__ */ new Date(),
1435
- trialEnd: new Date(trialEnd),
1436
- periodEnd: new Date(trialEnd)
1437
- } : {},
1438
- ...paystackSubscriptionCode !== void 0 ? { paystackSubscriptionCode } : {},
1439
- ...authorizationCode !== void 0 && authorizationCode !== null ? { paystackAuthorizationCode: authorizationCode } : {}
1440
- },
1441
- where: [{
1442
- field: "id",
1443
- value: targetSub.id
1444
- }]
1445
- });
1446
- if (updatedSubscription !== void 0 && updatedSubscription !== null && subscriptionOptions?.onSubscriptionComplete !== void 0) {
1447
- const plan = (await getPlans(subscriptionOptions)).find((p) => p.name.toLowerCase() === updatedSubscription.plan.toLowerCase());
1448
- if (plan !== void 0) await subscriptionOptions.onSubscriptionComplete({
1449
- event: data,
1450
- subscription: updatedSubscription,
1451
- plan
1452
- }, ctx);
1453
- }
1454
- } catch (e) {
1455
- ctx.context.logger.error("Failed to update transaction/subscription after verification", e);
1456
- }
1457
- }
1574
+ session: session.session
1575
+ } : void 0,
1576
+ throwOnError: true
1577
+ });
1578
+ if (!result.ok || result.data === null) throw new APIError("BAD_REQUEST", {
1579
+ code: result.error?.code ?? "FAILED_TO_VERIFY_TRANSACTION",
1580
+ message: result.error?.message ?? PAYSTACK_ERROR_CODES.FAILED_TO_VERIFY_TRANSACTION.message,
1581
+ status: result.error?.status
1582
+ });
1458
1583
  return ctx.json({
1459
- status,
1460
- reference,
1461
- data
1584
+ status: result.status,
1585
+ reference: result.reference,
1586
+ data: result.data
1462
1587
  });
1463
1588
  });
1464
1589
  };
@@ -2304,6 +2429,6 @@ const createPaystackPlugin = (options) => {
2304
2429
  };
2305
2430
  const paystack = createPaystackPlugin;
2306
2431
  //#endregion
2307
- export { chargeSubscriptionRenewal, paystack, syncPaystackPlans, syncPaystackProducts };
2432
+ export { chargeSubscriptionRenewal, paystack, reconcilePaystackTransaction, syncPaystackPlans, syncPaystackProducts };
2308
2433
 
2309
2434
  //# sourceMappingURL=index.mjs.map