@alexasomba/better-auth-paystack 2.4.3 → 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.
- package/README.md +42 -4
- package/dist/client.d.mts +1 -1
- package/dist/client.mjs +1 -1
- package/dist/index.d.mts +65 -3
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +338 -213
- package/dist/index.mjs.map +1 -1
- package/dist/{types-DHZSS1K6.d.mts → types-D-iIbe9E.d.mts} +9 -2
- package/dist/types-D-iIbe9E.d.mts.map +1 -0
- package/dist/version-DRyh6Dnz.mjs +6 -0
- package/dist/version-DRyh6Dnz.mjs.map +1 -0
- package/package.json +40 -36
- package/skills/{setup → better-auth-paystack-setup}/SKILL.md +2 -2
- package/skills/better-auth-paystack-setup/agents/openai.yaml +6 -0
- package/skills/{subscriptions-and-transactions → paystack-billing-flows}/SKILL.md +11 -3
- package/skills/paystack-billing-flows/agents/openai.yaml +6 -0
- package/skills/{billing-catalog-and-limits → paystack-catalog-limits}/SKILL.md +2 -2
- package/skills/paystack-catalog-limits/agents/openai.yaml +6 -0
- package/skills/paystack-client-api/SKILL.md +117 -0
- package/skills/paystack-client-api/agents/openai.yaml +6 -0
- package/skills/paystack-local-subscriptions/SKILL.md +97 -0
- package/skills/paystack-local-subscriptions/agents/openai.yaml +6 -0
- package/skills/{organization-billing → paystack-organization-billing}/SKILL.md +25 -4
- package/skills/paystack-organization-billing/agents/openai.yaml +6 -0
- package/skills/paystack-schema-migrations/SKILL.md +94 -0
- package/skills/paystack-schema-migrations/agents/openai.yaml +6 -0
- package/skills/{tanstack-start → paystack-tanstack-start}/SKILL.md +2 -2
- package/skills/paystack-tanstack-start/agents/openai.yaml +6 -0
- package/skills/paystack-testing-fixtures/SKILL.md +95 -0
- package/skills/paystack-testing-fixtures/agents/openai.yaml +6 -0
- package/skills/paystack-webhooks-events/SKILL.md +108 -0
- package/skills/paystack-webhooks-events/agents/openai.yaml +6 -0
- package/dist/types-DHZSS1K6.d.mts.map +0 -1
- package/dist/version-GQ15aLQo.mjs +0 -6
- package/dist/version-GQ15aLQo.mjs.map +0 -1
package/dist/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { t as PACKAGE_VERSION } from "./version-
|
|
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
|
|
429
|
-
function
|
|
430
|
-
|
|
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) =>
|
|
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:
|
|
1256
|
-
use:
|
|
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
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
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
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
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
|