payment-kit 1.23.10 → 1.24.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/blocklet.yml CHANGED
@@ -14,7 +14,7 @@ repository:
14
14
  type: git
15
15
  url: git+https://github.com/blocklet/payment-kit.git
16
16
  specVersion: 1.2.8
17
- version: 1.23.10
17
+ version: 1.24.0
18
18
  logo: logo.png
19
19
  files:
20
20
  - dist
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "payment-kit",
3
- "version": "1.23.10",
3
+ "version": "1.24.0",
4
4
  "scripts": {
5
5
  "dev": "blocklet dev --open",
6
6
  "prelint": "npm run types",
@@ -59,9 +59,9 @@
59
59
  "@blocklet/error": "^0.3.5",
60
60
  "@blocklet/js-sdk": "^1.17.8-beta-20260104-120132-cb5b1914",
61
61
  "@blocklet/logger": "^1.17.8-beta-20260104-120132-cb5b1914",
62
- "@blocklet/payment-broker-client": "1.23.10",
63
- "@blocklet/payment-react": "1.23.10",
64
- "@blocklet/payment-vendor": "1.23.10",
62
+ "@blocklet/payment-broker-client": "1.24.0",
63
+ "@blocklet/payment-react": "1.24.0",
64
+ "@blocklet/payment-vendor": "1.24.0",
65
65
  "@blocklet/sdk": "^1.17.8-beta-20260104-120132-cb5b1914",
66
66
  "@blocklet/ui-react": "^3.3.10",
67
67
  "@blocklet/uploader": "^0.3.19",
@@ -131,7 +131,7 @@
131
131
  "devDependencies": {
132
132
  "@abtnode/types": "^1.17.8-beta-20260104-120132-cb5b1914",
133
133
  "@arcblock/eslint-config-ts": "^0.3.3",
134
- "@blocklet/payment-types": "1.23.10",
134
+ "@blocklet/payment-types": "1.24.0",
135
135
  "@types/cookie-parser": "^1.4.9",
136
136
  "@types/cors": "^2.8.19",
137
137
  "@types/debug": "^4.1.12",
@@ -178,5 +178,5 @@
178
178
  "parser": "typescript"
179
179
  }
180
180
  },
181
- "gitHead": "9d058650e81ae43ffafb3e1253b50b6245d510ac"
181
+ "gitHead": "f3fad0829a1377422d51f9b6445a3502d0e94719"
182
182
  }
@@ -767,145 +767,388 @@ export default function PriceForm({ prefix = '', simple = false, productType = u
767
767
  expanded={isLocked}
768
768
  style={{ width: INPUT_WIDTH }}>
769
769
  <Box sx={{ width: INPUT_WIDTH, mb: 2, pl: 2, pr: 1 }}>
770
- {/* Credit 数量配置 */}
770
+ {/* 发放模式选择 - 仅对订阅类型显示 */}
771
+ {isRecurring && (
772
+ <Controller
773
+ name={getFieldName('metadata')}
774
+ control={control}
775
+ render={({ field }) => {
776
+ const deliveryMode = field.value?.credit_config?.schedule?.enabled ? 'periodic' : 'once';
777
+ return (
778
+ <Box sx={{ width: '100%', mb: 2 }}>
779
+ <FormLabel>{t('admin.creditProduct.deliveryMode.label')}</FormLabel>
780
+ <ToggleButtonGroup
781
+ value={deliveryMode}
782
+ exclusive
783
+ fullWidth
784
+ size="small"
785
+ onChange={(_, newMode) => {
786
+ if (!newMode) return;
787
+ const metadata = field.value || {};
788
+ const creditConfig = metadata.credit_config || {};
789
+ if (newMode === 'periodic') {
790
+ creditConfig.schedule = {
791
+ enabled: true,
792
+ delivery_mode: 'schedule',
793
+ interval_value: 1,
794
+ interval_unit: 'day',
795
+ first_grant_timing: 'immediate',
796
+ expire_with_next_grant: true,
797
+ };
798
+ // Clear one-time fields
799
+ delete creditConfig.credit_amount;
800
+ delete creditConfig.valid_duration_value;
801
+ delete creditConfig.valid_duration_unit;
802
+ } else {
803
+ delete creditConfig.schedule;
804
+ }
805
+ metadata.credit_config = creditConfig;
806
+ field.onChange(metadata);
807
+ }}>
808
+ <ToggleButton value="once">{t('admin.creditProduct.deliveryMode.once')}</ToggleButton>
809
+ <ToggleButton value="periodic">{t('admin.creditProduct.deliveryMode.periodic')}</ToggleButton>
810
+ </ToggleButtonGroup>
811
+ </Box>
812
+ );
813
+ }}
814
+ />
815
+ )}
816
+
817
+ {/* 一次性发放配置 */}
771
818
  <Controller
772
819
  name={getFieldName('metadata')}
773
820
  control={control}
774
- render={({ field }) => (
775
- <Box sx={{ width: '100%', mb: 2 }}>
776
- <FormLabel required>{t('admin.creditProduct.creditAmount.label')}</FormLabel>
777
- <TextField
778
- size="small"
779
- fullWidth
780
- type="number"
781
- placeholder={t('admin.creditProduct.creditAmount.placeholder')}
782
- value={field.value?.credit_config?.credit_amount || ''}
783
- disabled={isLocked}
784
- onChange={(e) => {
785
- const metadata = field.value || {};
786
- const creditConfig = metadata.credit_config || {};
787
- if (e.target.value) {
788
- creditConfig.credit_amount = e.target.value;
789
- } else {
790
- delete creditConfig.credit_amount;
791
- }
792
- metadata.credit_config = creditConfig;
793
- field.onChange(metadata);
794
- }}
795
- slotProps={{
796
- input: {
797
- endAdornment: (
798
- <InputAdornment position="end">
799
- <CurrencySelect
800
- mode="selected"
801
- hasSelected={(c) => currencies.fields.some((x: any) => x.currency_id === c.id)}
802
- currencyFilter={(c) => c.type === 'credit'}
803
- onSelect={(currencyId) => {
804
- const metadata = field.value || {};
805
- const creditConfig = metadata.credit_config || {};
806
- creditConfig.currency_id = currencyId;
807
- metadata.credit_config = creditConfig;
808
- field.onChange(metadata);
809
- }}
810
- value={field.value?.credit_config?.currency_id || creditCurrencies?.[0]?.id}
811
- disabled={isLocked}
812
- hideMethod
813
- selectSX={{ '.MuiOutlinedInput-notchedOutline': { border: 'none' } }}
814
- />
815
- </InputAdornment>
816
- ),
817
- },
818
- htmlInput: {
819
- min: 0,
820
- step: 'any',
821
- },
822
- }}
823
- />
824
- <Typography
825
- variant="caption"
826
- sx={{
827
- color: 'text.secondary',
828
- display: 'block',
829
- mt: 0.5,
830
- }}>
831
- {t('admin.creditProduct.creditAmount.description')}
832
- </Typography>
833
- </Box>
834
- )}
821
+ render={({ field }) => {
822
+ const isPeriodicMode = isRecurring && field.value?.credit_config?.schedule?.enabled;
823
+ if (isPeriodicMode) return <Box />;
824
+
825
+ return (
826
+ <>
827
+ {/* Credit 数量配置 */}
828
+ <Box sx={{ width: '100%', mb: 2 }}>
829
+ <FormLabel required>{t('admin.creditProduct.creditAmount.label')}</FormLabel>
830
+ <TextField
831
+ size="small"
832
+ fullWidth
833
+ type="number"
834
+ placeholder={t('admin.creditProduct.creditAmount.placeholder')}
835
+ value={field.value?.credit_config?.credit_amount || ''}
836
+ disabled={isLocked}
837
+ onChange={(e) => {
838
+ const metadata = field.value || {};
839
+ const creditConfig = metadata.credit_config || {};
840
+ if (e.target.value) {
841
+ creditConfig.credit_amount = e.target.value;
842
+ } else {
843
+ delete creditConfig.credit_amount;
844
+ }
845
+ metadata.credit_config = creditConfig;
846
+ field.onChange(metadata);
847
+ }}
848
+ slotProps={{
849
+ input: {
850
+ endAdornment: (
851
+ <InputAdornment position="end">
852
+ <CurrencySelect
853
+ mode="selected"
854
+ hasSelected={(c) => currencies.fields.some((x: any) => x.currency_id === c.id)}
855
+ currencyFilter={(c) => c.type === 'credit'}
856
+ onSelect={(currencyId) => {
857
+ const metadata = field.value || {};
858
+ const creditConfig = metadata.credit_config || {};
859
+ creditConfig.currency_id = currencyId;
860
+ metadata.credit_config = creditConfig;
861
+ field.onChange(metadata);
862
+ }}
863
+ value={field.value?.credit_config?.currency_id || creditCurrencies?.[0]?.id}
864
+ disabled={isLocked}
865
+ hideMethod
866
+ selectSX={{ '.MuiOutlinedInput-notchedOutline': { border: 'none' } }}
867
+ />
868
+ </InputAdornment>
869
+ ),
870
+ },
871
+ htmlInput: {
872
+ min: 0,
873
+ step: 'any',
874
+ },
875
+ }}
876
+ />
877
+ <Typography
878
+ variant="caption"
879
+ sx={{
880
+ color: 'text.secondary',
881
+ display: 'block',
882
+ mt: 0.5,
883
+ }}>
884
+ {t('admin.creditProduct.creditAmount.description')}
885
+ </Typography>
886
+ </Box>
887
+
888
+ {/* 可用时长配置 */}
889
+ <Box sx={{ width: '100%', mb: 2 }}>
890
+ <FormLabel tooltip={t('admin.creditProduct.validDuration.help')}>
891
+ {t('admin.creditProduct.validDuration.label')}
892
+ </FormLabel>
893
+ <Stack
894
+ direction="row"
895
+ spacing={1}
896
+ sx={{
897
+ alignItems: 'center',
898
+ }}>
899
+ <TextField
900
+ size="small"
901
+ type="number"
902
+ sx={{ flex: 1 }}
903
+ value={field.value?.credit_config?.valid_duration_value ?? '0'}
904
+ placeholder="0"
905
+ disabled={isLocked}
906
+ onChange={(e) => {
907
+ const metadata = field.value || {};
908
+ const creditConfig = metadata.credit_config || {};
909
+ const value = +e.target.value;
910
+ if (!Number.isNaN(value) && value >= 0) {
911
+ creditConfig.valid_duration_value = +value;
912
+ } else {
913
+ delete creditConfig.valid_duration_value;
914
+ }
915
+ metadata.credit_config = creditConfig;
916
+ field.onChange(metadata);
917
+ }}
918
+ slotProps={{
919
+ htmlInput: {
920
+ min: 0,
921
+ step: 0.1,
922
+ },
923
+ }}
924
+ />
925
+ <Select
926
+ size="small"
927
+ sx={{ minWidth: 120 }}
928
+ disabled={isLocked}
929
+ value={field.value?.credit_config?.valid_duration_unit || 'days'}
930
+ onChange={(e) => {
931
+ const metadata = field.value || {};
932
+ const creditConfig = metadata.credit_config || {};
933
+ creditConfig.valid_duration_unit = e.target.value;
934
+ metadata.credit_config = creditConfig;
935
+ field.onChange(metadata);
936
+ }}>
937
+ {!livemode && (
938
+ <MenuItem value="hours">{t('admin.creditProduct.validDuration.hours')}</MenuItem>
939
+ )}
940
+ <MenuItem value="days">{t('admin.creditProduct.validDuration.days')}</MenuItem>
941
+ <MenuItem value="weeks">{t('admin.creditProduct.validDuration.weeks')}</MenuItem>
942
+ <MenuItem value="months">{t('admin.creditProduct.validDuration.months')}</MenuItem>
943
+ <MenuItem value="years">{t('admin.creditProduct.validDuration.years')}</MenuItem>
944
+ </Select>
945
+ </Stack>
946
+ <Typography
947
+ variant="caption"
948
+ sx={{
949
+ color: 'text.secondary',
950
+ display: 'block',
951
+ mt: 0.5,
952
+ }}>
953
+ {t('admin.creditProduct.validDuration.description')}
954
+ </Typography>
955
+ </Box>
956
+ </>
957
+ );
958
+ }}
835
959
  />
836
960
 
837
- {/* 可用时长配置 */}
838
- <Controller
839
- name={getFieldName('metadata')}
840
- control={control}
841
- render={({ field }) => (
842
- <Box sx={{ width: '100%', mb: 2 }}>
843
- <FormLabel tooltip={t('admin.creditProduct.validDuration.help')}>
844
- {t('admin.creditProduct.validDuration.label')}
845
- </FormLabel>
846
- <Stack
847
- direction="row"
848
- spacing={1}
849
- sx={{
850
- alignItems: 'center',
851
- }}>
852
- <TextField
853
- size="small"
854
- type="number"
855
- sx={{ flex: 1 }}
856
- value={field.value?.credit_config?.valid_duration_value ?? '0'}
857
- placeholder="0"
858
- disabled={isLocked}
859
- onChange={(e) => {
860
- const metadata = field.value || {};
861
- const creditConfig = metadata.credit_config || {};
862
- const value = +e.target.value;
863
- if (!Number.isNaN(value) && value >= 0) {
864
- creditConfig.valid_duration_value = +value;
865
- } else {
866
- delete creditConfig.valid_duration_value;
961
+ {/* 周期性发放配置 */}
962
+ {isRecurring && (
963
+ <Controller
964
+ name={getFieldName('metadata')}
965
+ control={control}
966
+ render={({ field }) => {
967
+ const isPeriodicMode = field.value?.credit_config?.schedule?.enabled;
968
+ if (!isPeriodicMode) return <Box />;
969
+
970
+ const expireWithNextGrant = field.value?.credit_config?.schedule?.expire_with_next_grant ?? true;
971
+
972
+ return (
973
+ <Stack spacing={2} sx={{ mb: 2 }}>
974
+ {/* 发放间隔 */}
975
+ <Box>
976
+ <FormLabel>{t('admin.creditProduct.schedule.interval.label')}</FormLabel>
977
+ <Stack direction="row" spacing={1} alignItems="center">
978
+ <TextField
979
+ size="small"
980
+ type="number"
981
+ sx={{ flex: 1 }}
982
+ value={field.value?.credit_config?.schedule?.interval_value || 1}
983
+ onChange={(e) => {
984
+ const metadata = field.value || {};
985
+ const creditConfig = metadata.credit_config || {};
986
+ const schedule = creditConfig.schedule || {};
987
+ schedule.interval_value = Math.max(0.01, parseFloat(e.target.value) || 1);
988
+ creditConfig.schedule = schedule;
989
+ metadata.credit_config = creditConfig;
990
+ field.onChange(metadata);
991
+ }}
992
+ slotProps={{ htmlInput: { min: 0.01, step: 0.01 } }}
993
+ />
994
+ <Select
995
+ size="small"
996
+ sx={{ minWidth: 100 }}
997
+ value={field.value?.credit_config?.schedule?.interval_unit || 'day'}
998
+ onChange={(e) => {
999
+ const metadata = field.value || {};
1000
+ const creditConfig = metadata.credit_config || {};
1001
+ const schedule = creditConfig.schedule || {};
1002
+ schedule.interval_unit = e.target.value;
1003
+ creditConfig.schedule = schedule;
1004
+ metadata.credit_config = creditConfig;
1005
+ field.onChange(metadata);
1006
+ }}>
1007
+ {!livemode && (
1008
+ <MenuItem value="hour">{t('admin.creditProduct.schedule.interval.hour')}</MenuItem>
1009
+ )}
1010
+ <MenuItem value="day">{t('admin.creditProduct.schedule.interval.day')}</MenuItem>
1011
+ <MenuItem value="week">{t('admin.creditProduct.schedule.interval.week')}</MenuItem>
1012
+ <MenuItem value="month">{t('admin.creditProduct.schedule.interval.month')}</MenuItem>
1013
+ </Select>
1014
+ </Stack>
1015
+ </Box>
1016
+
1017
+ {/* 每次发放数量(带货币选择) */}
1018
+ <Box>
1019
+ <FormLabel required>{t('admin.creditProduct.schedule.amountPerGrant.label')}</FormLabel>
1020
+ <TextField
1021
+ size="small"
1022
+ type="number"
1023
+ fullWidth
1024
+ required
1025
+ value={field.value?.credit_config?.schedule?.amount_per_grant || ''}
1026
+ onChange={(e) => {
1027
+ const metadata = field.value || {};
1028
+ const creditConfig = metadata.credit_config || {};
1029
+ const schedule = creditConfig.schedule || {};
1030
+ schedule.amount_per_grant = e.target.value;
1031
+ creditConfig.schedule = schedule;
1032
+ metadata.credit_config = creditConfig;
1033
+ field.onChange(metadata);
1034
+ }}
1035
+ slotProps={{
1036
+ input: {
1037
+ endAdornment: (
1038
+ <InputAdornment position="end">
1039
+ <CurrencySelect
1040
+ mode="selected"
1041
+ hasSelected={(c) => currencies.fields.some((x: any) => x.currency_id === c.id)}
1042
+ currencyFilter={(c) => c.type === 'credit'}
1043
+ onSelect={(currencyId) => {
1044
+ const metadata = field.value || {};
1045
+ const creditConfig = metadata.credit_config || {};
1046
+ creditConfig.currency_id = currencyId;
1047
+ metadata.credit_config = creditConfig;
1048
+ field.onChange(metadata);
1049
+ }}
1050
+ value={field.value?.credit_config?.currency_id || creditCurrencies?.[0]?.id}
1051
+ disabled={isLocked}
1052
+ hideMethod
1053
+ selectSX={{ '.MuiOutlinedInput-notchedOutline': { border: 'none' } }}
1054
+ />
1055
+ </InputAdornment>
1056
+ ),
1057
+ },
1058
+ htmlInput: { min: 0, step: 'any' },
1059
+ }}
1060
+ />
1061
+ <Typography variant="caption" sx={{ color: 'text.secondary', display: 'block', mt: 0.5 }}>
1062
+ {t('admin.creditProduct.schedule.amountPerGrant.description')}
1063
+ </Typography>
1064
+ </Box>
1065
+
1066
+ {/* 可用时长配置 */}
1067
+ <Box>
1068
+ <FormLabel tooltip={t('admin.creditProduct.validDuration.help')}>
1069
+ {t('admin.creditProduct.validDuration.label')}
1070
+ </FormLabel>
1071
+ <Stack direction="row" spacing={1} sx={{ alignItems: 'center' }}>
1072
+ <TextField
1073
+ size="small"
1074
+ type="number"
1075
+ sx={{ flex: 1 }}
1076
+ value={field.value?.credit_config?.valid_duration_value ?? '0'}
1077
+ placeholder="0"
1078
+ disabled={isLocked || expireWithNextGrant}
1079
+ onChange={(e) => {
1080
+ const metadata = field.value || {};
1081
+ const creditConfig = metadata.credit_config || {};
1082
+ const value = +e.target.value;
1083
+ if (!Number.isNaN(value) && value >= 0) {
1084
+ creditConfig.valid_duration_value = +value;
1085
+ } else {
1086
+ delete creditConfig.valid_duration_value;
1087
+ }
1088
+ metadata.credit_config = creditConfig;
1089
+ field.onChange(metadata);
1090
+ }}
1091
+ slotProps={{ htmlInput: { min: 0, step: 0.1 } }}
1092
+ />
1093
+ <Select
1094
+ size="small"
1095
+ sx={{ minWidth: 120 }}
1096
+ disabled={isLocked || expireWithNextGrant}
1097
+ value={field.value?.credit_config?.valid_duration_unit || 'days'}
1098
+ onChange={(e) => {
1099
+ const metadata = field.value || {};
1100
+ const creditConfig = metadata.credit_config || {};
1101
+ creditConfig.valid_duration_unit = e.target.value;
1102
+ metadata.credit_config = creditConfig;
1103
+ field.onChange(metadata);
1104
+ }}>
1105
+ {!livemode && (
1106
+ <MenuItem value="hours">{t('admin.creditProduct.validDuration.hours')}</MenuItem>
1107
+ )}
1108
+ <MenuItem value="days">{t('admin.creditProduct.validDuration.days')}</MenuItem>
1109
+ <MenuItem value="weeks">{t('admin.creditProduct.validDuration.weeks')}</MenuItem>
1110
+ <MenuItem value="months">{t('admin.creditProduct.validDuration.months')}</MenuItem>
1111
+ <MenuItem value="years">{t('admin.creditProduct.validDuration.years')}</MenuItem>
1112
+ </Select>
1113
+ </Stack>
1114
+ <Typography variant="caption" sx={{ color: 'text.secondary', display: 'block', mt: 0.5 }}>
1115
+ {t('admin.creditProduct.validDuration.description')}
1116
+ </Typography>
1117
+ </Box>
1118
+
1119
+ {/* 随下次发放过期 */}
1120
+ <FormControlLabel
1121
+ sx={{ alignItems: 'flex-start' }}
1122
+ control={
1123
+ <Checkbox
1124
+ checked={expireWithNextGrant}
1125
+ onChange={(_, checked) => {
1126
+ const metadata = field.value || {};
1127
+ const creditConfig = metadata.credit_config || {};
1128
+ const schedule = creditConfig.schedule || {};
1129
+ schedule.expire_with_next_grant = checked;
1130
+ creditConfig.schedule = schedule;
1131
+ metadata.credit_config = creditConfig;
1132
+ field.onChange(metadata);
1133
+ }}
1134
+ />
867
1135
  }
868
- metadata.credit_config = creditConfig;
869
- field.onChange(metadata);
870
- }}
871
- slotProps={{
872
- htmlInput: {
873
- min: 0,
874
- step: 0.1,
875
- },
876
- }}
877
- />
878
- <Select
879
- size="small"
880
- sx={{ minWidth: 120 }}
881
- disabled={isLocked}
882
- value={field.value?.credit_config?.valid_duration_unit || 'days'}
883
- onChange={(e) => {
884
- const metadata = field.value || {};
885
- const creditConfig = metadata.credit_config || {};
886
- creditConfig.valid_duration_unit = e.target.value;
887
- metadata.credit_config = creditConfig;
888
- field.onChange(metadata);
889
- }}>
890
- {!livemode && <MenuItem value="hours">{t('admin.creditProduct.validDuration.hours')}</MenuItem>}
891
- <MenuItem value="days">{t('admin.creditProduct.validDuration.days')}</MenuItem>
892
- <MenuItem value="weeks">{t('admin.creditProduct.validDuration.weeks')}</MenuItem>
893
- <MenuItem value="months">{t('admin.creditProduct.validDuration.months')}</MenuItem>
894
- <MenuItem value="years">{t('admin.creditProduct.validDuration.years')}</MenuItem>
895
- </Select>
896
- </Stack>
897
- <Typography
898
- variant="caption"
899
- sx={{
900
- color: 'text.secondary',
901
- display: 'block',
902
- mt: 0.5,
903
- }}>
904
- {t('admin.creditProduct.validDuration.description')}
905
- </Typography>
906
- </Box>
907
- )}
908
- />
1136
+ label={
1137
+ <Box>
1138
+ <Typography sx={{ color: 'text.primary' }}>
1139
+ {t('admin.creditProduct.schedule.expireWithNextGrant.label')}
1140
+ </Typography>
1141
+ <Typography variant="caption" sx={{ color: 'text.secondary' }}>
1142
+ {t('admin.creditProduct.schedule.expireWithNextGrant.description')}
1143
+ </Typography>
1144
+ </Box>
1145
+ }
1146
+ />
1147
+ </Stack>
1148
+ );
1149
+ }}
1150
+ />
1151
+ )}
909
1152
 
910
1153
  {/* 关联特定价格 */}
911
1154
  <Controller
@@ -63,6 +63,12 @@ export default function EditPrice({ price, loading, onSave, onCancel, productTyp
63
63
  return;
64
64
  }
65
65
  handleSubmit(async (formData: any) => {
66
+ // Workaround: handleSubmit may not include metadata when Controller returns empty element
67
+ // Use getValues('metadata') as fallback
68
+ if (formData.metadata === undefined) {
69
+ formData.metadata = methods.getValues('metadata');
70
+ }
71
+
66
72
  if (
67
73
  Number(formData.quantity_available) > 0 &&
68
74
  Number(formData.quantity_available) < Number(formData.quantity_sold)
@@ -153,8 +153,15 @@ export function SubscriptionActionsInner({
153
153
  return true;
154
154
  };
155
155
 
156
+ const isScheduledCreditPrice = (price: any) => {
157
+ const schedule = price?.metadata?.credit_config?.schedule;
158
+ return schedule?.enabled && schedule?.delivery_mode === 'schedule';
159
+ };
160
+
161
+ const hasScheduledCredit = (subscription.items || []).some((item: any) => isScheduledCreditPrice(item.price));
162
+
156
163
  const { data: changePlanAvailable = false } = useRequest(() => fetchChangePlan(subscription.id), {
157
- ready: !!showExtra && isActive(subscription),
164
+ ready: !!showExtra && isActive(subscription) && !hasScheduledCredit,
158
165
  });
159
166
 
160
167
  const { data: batchPayAvailable = '' } = useRequest(() => fetchBatchPay(subscription.id), {
@@ -491,7 +498,7 @@ export function SubscriptionActionsInner({
491
498
  },
492
499
  {
493
500
  key: 'changePlan',
494
- show: changePlanAvailable,
501
+ show: changePlanAvailable && !hasScheduledCredit,
495
502
  label: action?.text || t('payment.customer.changePlan.button'),
496
503
  onClick: (e) => {
497
504
  e?.stopPropagation();