@@ -103,7 +103,7 @@ public class ApiConstants { | |||
public static final String CLOUDS_ENDPOINT = "/clouds"; | |||
public static final String ROLES_ENDPOINT = "/roles"; | |||
public static final String PROXY_ENDPOINT = "/p"; | |||
public static final String DATA_ENDPOINT = "/d"; | |||
public static final String PROMOTIONS_ENDPOINT = "/promos"; | |||
public static final String NOTIFY_ENDPOINT = "/notify"; | |||
public static final String EP_READ_METADATA = "/meta"; | |||
@@ -0,0 +1,12 @@ | |||
package bubble.cloud.payment; | |||
import bubble.model.account.Account; | |||
import bubble.model.bill.Promotion; | |||
public interface PromotionalPaymentServiceDriver extends PaymentServiceDriver { | |||
void applyPromo(Promotion promo, Account caller); | |||
void applyReferralPromo(Promotion referralPromo, Account caller, Account referredFrom); | |||
} |
@@ -0,0 +1,33 @@ | |||
package bubble.cloud.payment.firstMonthFree; | |||
import bubble.cloud.payment.PaymentDriverBase; | |||
import bubble.cloud.payment.PromotionalPaymentServiceDriver; | |||
import bubble.model.account.Account; | |||
import bubble.model.bill.*; | |||
import bubble.notify.payment.PaymentValidationResult; | |||
import static org.cobbzilla.util.daemon.ZillaRuntime.notSupported; | |||
public class FirstMonthFreePaymentDriver extends PaymentDriverBase<FirstMonthPaymentConfig> implements PromotionalPaymentServiceDriver { | |||
@Override public PaymentMethodType getPaymentMethodType() { return PaymentMethodType.promotional_credit; } | |||
@Override public void applyPromo(Promotion promo, Account caller) { | |||
// todo | |||
} | |||
@Override public void applyReferralPromo(Promotion referralPromo, Account caller, Account referredFrom) { notSupported("applyReferralPromo"); } | |||
@Override public PaymentValidationResult validate(AccountPaymentMethod paymentMethod) { | |||
return null; | |||
} | |||
@Override protected String charge(BubblePlan plan, AccountPlan accountPlan, AccountPaymentMethod paymentMethod, Bill bill) { | |||
return null; | |||
} | |||
@Override protected String refund(AccountPlan accountPlan, AccountPayment payment, AccountPaymentMethod paymentMethod, Bill bill, long refundAmount) { | |||
return null; | |||
} | |||
} |
@@ -0,0 +1,3 @@ | |||
package bubble.cloud.payment.firstMonthFree; | |||
public class FirstMonthPaymentConfig {} |
@@ -0,0 +1,47 @@ | |||
package bubble.cloud.payment.referralMonthFree; | |||
import bubble.cloud.payment.PaymentDriverBase; | |||
import bubble.cloud.payment.PromotionalPaymentServiceDriver; | |||
import bubble.model.account.Account; | |||
import bubble.model.bill.*; | |||
import bubble.notify.payment.PaymentValidationResult; | |||
import static org.cobbzilla.util.daemon.ZillaRuntime.notSupported; | |||
import static org.cobbzilla.wizard.resources.ResourceUtil.invalidEx; | |||
public class ReferralMonthFreePaymentDriver extends PaymentDriverBase<ReferralMonthPaymentConfig> implements PromotionalPaymentServiceDriver { | |||
@Override public PaymentMethodType getPaymentMethodType() { return PaymentMethodType.promotional_credit; } | |||
@Override public void applyPromo(Promotion promo, Account caller) { notSupported("applyPromo"); } | |||
@Override public void applyReferralPromo(Promotion referralPromo, Account caller, Account referredFrom) { | |||
// todo | |||
// validate referralPromo | |||
// check existing AccountPaymentMethods for caller, they can only have one AccountPaymentMethod of the "joiner" type across all methods | |||
// -- create if not exist | |||
// check existing AccountPaymentMethods for referredFrom, they can only have one AccountPaymentMethod of the "referral" type for the caller | |||
// -- create if not exist | |||
} | |||
@Override public PaymentValidationResult validate(AccountPaymentMethod paymentMethod) { | |||
// todo | |||
// validate that this paymentMethod is for this driver | |||
// validate that this paymentMethod has not yet been used on any other AccountPayment | |||
return null; | |||
} | |||
@Override protected String charge(BubblePlan plan, AccountPlan accountPlan, AccountPaymentMethod paymentMethod, Bill bill) { | |||
// todo | |||
// validate that this paymentMethod is for this driver | |||
// validate that this paymentMethod has not yet been used on any other AccountPayment | |||
// apply the paymentMethod | |||
return null; | |||
} | |||
@Override protected String refund(AccountPlan accountPlan, AccountPayment payment, AccountPaymentMethod paymentMethod, Bill bill, long refundAmount) { | |||
// cannot refund | |||
throw invalidEx("err.refund.noRefundsForPromotionalCredits"); | |||
} | |||
} |
@@ -0,0 +1,3 @@ | |||
package bubble.cloud.payment.referralMonthFree; | |||
public class ReferralMonthPaymentConfig {} |
@@ -0,0 +1,51 @@ | |||
package bubble.dao.bill; | |||
import bubble.model.bill.Promotion; | |||
import org.cobbzilla.wizard.dao.AbstractCRUDDAO; | |||
import org.hibernate.criterion.Order; | |||
import org.springframework.stereotype.Repository; | |||
import java.util.List; | |||
import static org.cobbzilla.util.daemon.ZillaRuntime.empty; | |||
import static org.hibernate.criterion.Restrictions.*; | |||
@Repository | |||
public class PromotionDAO extends AbstractCRUDDAO<Promotion> { | |||
public static final Order PRIORITY_DESC = Order.desc("priority"); | |||
@Override public Order getDefaultSortOrder() { return PRIORITY_DESC; } | |||
public Promotion findByName(String name) { return findByUniqueField("name", name); } | |||
public Promotion findById(String id) { | |||
final Promotion found = findByUuid(id); | |||
return found != null ? found : findByName(id); | |||
} | |||
public Promotion findEnabledWithCode(String code) { | |||
return findByUniqueFields("enabled", true, "code", code); | |||
} | |||
public Promotion findEnabledWithNoCode() { | |||
final List<Promotion> promos = findByFields("enabled", true, "code", null); | |||
return empty(promos) ? null : promos.get(0); | |||
} | |||
public List<Promotion> findEnabledAndNoCodeOrWithCode(String code) { | |||
if (empty(code)) { | |||
return findByFields("enabled", true, "code", null); | |||
} else { | |||
return list(criteria().add(and( | |||
eq("enabled", true), | |||
or(isNull("code"), eq("code", code))))); | |||
} | |||
} | |||
public Promotion findReferralPromotion() { | |||
final List<Promotion> referrals = findByField("referral", true); | |||
return empty(referrals) ? null : referrals.get(0); | |||
} | |||
} |
@@ -60,67 +60,79 @@ public class AccountPlan extends IdentifiableBase implements HasAccount { | |||
@Column(nullable=false, updatable=false, length=UUID_MAXLEN) | |||
@Getter @Setter private String account; | |||
// refers to an Account.uuid, but we do not use a foreign key, so if the referring Account is deleted | |||
// then a lookup of the referralFrom will return null, and any unused referral promotion cannot be used | |||
@ECSearchable @ECField(index=30) | |||
@Column(length=UUID_MAXLEN, updatable=false) | |||
@Getter @Setter private String referralFrom; | |||
public boolean hasReferralFrom () { return !empty(referralFrom); } | |||
@ECSearchable @ECField(index=40) | |||
@Column(length=100, updatable=false) | |||
@Getter @Setter private String promoCode; | |||
public boolean hasPromoCode () { return !empty(promoCode); } | |||
@ECSearchable @ECField(index=50) | |||
@ECForeignKey(entity=BubblePlan.class) | |||
@Column(nullable=false, updatable=false, length=UUID_MAXLEN) | |||
@Getter @Setter private String plan; | |||
@ECSearchable @ECField(index=40) | |||
@ECSearchable @ECField(index=60) | |||
@ECForeignKey(entity=AccountPaymentMethod.class) | |||
@Column(updatable=false, length=UUID_MAXLEN) | |||
@Getter @Setter private String paymentMethod; | |||
@ECSearchable @ECField(index=50) | |||
@ECSearchable @ECField(index=70) | |||
@ECForeignKey(entity=BubbleDomain.class) | |||
@Column(nullable=false, updatable=false, length=UUID_MAXLEN) | |||
@Getter @Setter private String domain; | |||
@ECSearchable @ECField(index=60) | |||
@ECSearchable @ECField(index=80) | |||
@ECForeignKey(entity=BubbleNetwork.class, index=false) @ECIndex(unique=true) | |||
@Column(length=UUID_MAXLEN) | |||
@Getter @Setter private String network; | |||
@ECSearchable @ECField(index=70) | |||
@ECSearchable @ECField(index=90) | |||
@ECForeignKey(entity=AccountSshKey.class) | |||
@Column(length=UUID_MAXLEN) | |||
@Getter @Setter private String sshKey; | |||
public boolean hasSshKey () { return !empty(sshKey); } | |||
@ECSearchable @ECField(index=80) | |||
@ECSearchable @ECField(index=100) | |||
@Column(nullable=false) | |||
@Getter @Setter private Boolean enabled = false; | |||
public boolean enabled() { return bool(enabled); } | |||
public boolean disabled() { return !enabled(); } | |||
@ECSearchable(type=EntityFieldType.epoch_time) @ECField(index=90) | |||
@ECSearchable(type=EntityFieldType.epoch_time) @ECField(index=110) | |||
@Column(nullable=false) | |||
@ECIndex @Getter @Setter private Long nextBill; | |||
@ECSearchable @ECField(index=100) | |||
@ECSearchable @ECField(index=120) | |||
@Column(nullable=false, length=50) | |||
@Getter @Setter private String nextBillDate; | |||
public AccountPlan setNextBillDate() { return setNextBillDate(BILL_START_END_FORMAT.print(getNextBill())); } | |||
@ECSearchable @ECField(index=110) | |||
@ECSearchable @ECField(index=130) | |||
@ECIndex @Getter @Setter private Long deleted; | |||
public boolean deleted() { return deleted != null; } | |||
public boolean notDeleted() { return !deleted(); } | |||
@ECSearchable @ECField(index=120) | |||
@ECSearchable @ECField(index=140) | |||
@Column(nullable=false) | |||
@ECIndex @Getter @Setter private Boolean closed = false; | |||
public boolean closed() { return bool(closed); } | |||
public boolean notClosed() { return !closed(); } | |||
@ECSearchable @ECField(index=130, type=EntityFieldType.reference) | |||
@ECSearchable @ECField(index=150, type=EntityFieldType.reference) | |||
@ECIndex(unique=true) @Column(length=UUID_MAXLEN) | |||
@Getter @Setter private String deletedNetwork; | |||
public boolean hasDeletedNetwork() { return deletedNetwork != null; } | |||
@ECSearchable @ECField(index=140) @Column(nullable=false) | |||
@ECSearchable @ECField(index=160) @Column(nullable=false) | |||
@Getter @Setter private Boolean refundIssued = false; | |||
@ECSearchable @ECField(index=150, type=EntityFieldType.error) | |||
@ECSearchable @ECField(index=170, type=EntityFieldType.error) | |||
@Getter @Setter private String refundError; | |||
// Fields below are used when creating a new plan, to also create the network associated with it | |||
@@ -6,7 +6,7 @@ import static bubble.ApiConstants.enumFromString; | |||
public enum PaymentMethodType { | |||
credit, code, free; | |||
credit, code, free, promotional_credit; | |||
public boolean requiresClaim() { return this == code; } | |||
public boolean requiresAuth() { return this == credit; } | |||
@@ -0,0 +1,73 @@ | |||
package bubble.model.bill; | |||
import bubble.model.cloud.CloudService; | |||
import lombok.Getter; | |||
import lombok.NoArgsConstructor; | |||
import lombok.Setter; | |||
import lombok.experimental.Accessors; | |||
import org.cobbzilla.util.collection.ArrayUtil; | |||
import org.cobbzilla.util.collection.HasPriority; | |||
import org.cobbzilla.wizard.model.Identifiable; | |||
import org.cobbzilla.wizard.model.IdentifiableBase; | |||
import org.cobbzilla.wizard.model.NamedEntity; | |||
import org.cobbzilla.wizard.model.entityconfig.annotations.*; | |||
import org.cobbzilla.wizard.validation.HasValue; | |||
import javax.persistence.Column; | |||
import javax.persistence.Entity; | |||
import static bubble.ApiConstants.PROMOTIONS_ENDPOINT; | |||
import static org.cobbzilla.util.daemon.ZillaRuntime.now; | |||
import static org.cobbzilla.util.reflect.ReflectionUtil.copy; | |||
@ECType(root=true) | |||
@ECTypeURIs(baseURI=PROMOTIONS_ENDPOINT, listFields={"name", "enabled", "start", "end"}) | |||
@Entity @NoArgsConstructor @Accessors(chain=true) | |||
public class Promotion extends IdentifiableBase implements NamedEntity, HasPriority { | |||
public static final String[] UPDATE_FIELDS = {"enabled", "start", "end"}; | |||
public static final String[] CREATE_FIELDS = ArrayUtil.append(UPDATE_FIELDS, "name", "referral"); | |||
public Promotion (Promotion other) { copy(this, other, CREATE_FIELDS); } | |||
@Override public Identifiable update(Identifiable other) { copy(this, other, UPDATE_FIELDS); return this; } | |||
@ECSearchable(filter=true) @ECField(index=10) | |||
@HasValue(message="err.name.required") | |||
@ECIndex(unique=true) @Column(nullable=false, updatable=false, length=NAME_MAXLEN) | |||
@Getter @Setter private String name; | |||
@ECSearchable @ECField(index=20) | |||
@ECForeignKey(entity=CloudService.class) | |||
@Column(nullable=false, updatable=false, length=UUID_MAXLEN) | |||
@Getter @Setter private String cloud; | |||
@ECSearchable @ECField(index=30) @Column(nullable=false) | |||
@ECIndex @Getter @Setter private Integer priority = 1; | |||
@ECSearchable(filter=true) @ECField(index=40) | |||
@ECIndex(unique=true) @Column(updatable=false, length=NAME_MAXLEN) | |||
@Getter @Setter private String code; | |||
@ECSearchable @ECField(index=50) | |||
@ECIndex @Column(nullable=false) | |||
@Getter @Setter private Boolean enabled = true; | |||
public boolean enabled () { return enabled == null || enabled; } | |||
@ECSearchable @ECField(index=60) | |||
@ECIndex @Getter @Setter private Long start; | |||
public boolean hasStarted () { return start == null || start > now(); } | |||
@ECSearchable @ECField(index=70) | |||
@ECIndex @Getter @Setter private Long end; | |||
public boolean hasEnded () { return end != null && end > now(); } | |||
public boolean active () { return enabled() && hasStarted() && !hasEnded(); } | |||
public boolean inactive () { return !active(); } | |||
@ECSearchable @ECField(index=80) | |||
@ECIndex @Column(nullable=false) | |||
@Getter @Setter private Boolean referral = false; | |||
public boolean referral () { return referral != null && referral; } | |||
} |
@@ -2,19 +2,20 @@ package bubble.resources.bill; | |||
import bubble.cloud.CloudServiceType; | |||
import bubble.cloud.geoLocation.GeoLocation; | |||
import bubble.cloud.payment.PaymentServiceDriver; | |||
import bubble.cloud.payment.PromotionalPaymentServiceDriver; | |||
import bubble.dao.account.AccountSshKeyDAO; | |||
import bubble.dao.bill.AccountPaymentMethodDAO; | |||
import bubble.dao.bill.AccountPlanDAO; | |||
import bubble.dao.bill.BubblePlanDAO; | |||
import bubble.dao.bill.PromotionDAO; | |||
import bubble.dao.cloud.BubbleDomainDAO; | |||
import bubble.dao.cloud.BubbleFootprintDAO; | |||
import bubble.dao.cloud.BubbleNetworkDAO; | |||
import bubble.dao.cloud.CloudServiceDAO; | |||
import bubble.model.account.Account; | |||
import bubble.model.account.AccountSshKey; | |||
import bubble.model.bill.AccountPaymentMethod; | |||
import bubble.model.bill.AccountPlan; | |||
import bubble.model.bill.BubblePlan; | |||
import bubble.model.bill.*; | |||
import bubble.model.cloud.BubbleDomain; | |||
import bubble.model.cloud.BubbleFootprint; | |||
import bubble.model.cloud.BubbleNetwork; | |||
@@ -57,6 +58,7 @@ public class AccountPlansResource extends AccountOwnedResource<AccountPlan, Acco | |||
@Autowired private BubbleConfiguration configuration; | |||
@Autowired private AuthenticatorService authenticatorService; | |||
@Autowired private GeoService geoService; | |||
@Autowired private PromotionDAO promotionDAO; | |||
public AccountPlansResource(Account account) { super(account); } | |||
@@ -189,6 +191,57 @@ public class AccountPlansResource extends AccountOwnedResource<AccountPlan, Acco | |||
paymentMethod.setAccount(caller.getUuid()).validate(errors, configuration); | |||
} | |||
} | |||
if (request.hasReferralFrom()) { | |||
final Account referredFrom = accountDAO.findByName(request.getReferralFrom()); | |||
if (referredFrom == null || referredFrom.deleted()) { | |||
errors.addViolation("err.referralFrom.invalid"); | |||
} | |||
// check for referral promotion | |||
final Promotion referralPromo = promotionDAO.findReferralPromotion(); | |||
if (referralPromo == null) { | |||
errors.addViolation("err.referralFrom.unavailable"); | |||
} else { | |||
final CloudService referralCloud = cloudDAO.findByUuid(referralPromo.getCloud()); | |||
if (referralCloud == null || referralCloud.getType() != CloudServiceType.payment) { | |||
errors.addViolation("err.referralFrom.configurationError"); | |||
} else { | |||
final PaymentServiceDriver referralDriver = referralCloud.getPaymentDriver(configuration); | |||
if (referralDriver.getPaymentMethodType() != PaymentMethodType.promotional_credit | |||
|| !(referralDriver instanceof PromotionalPaymentServiceDriver)) { | |||
errors.addViolation("err.referralFrom.configurationError"); | |||
} else { | |||
final PromotionalPaymentServiceDriver promoDriver = (PromotionalPaymentServiceDriver) referralDriver; | |||
promoDriver.applyReferralPromo(referralPromo, caller, referredFrom); | |||
} | |||
} | |||
} | |||
} | |||
final Promotion promo; | |||
if (request.hasPromoCode()) { | |||
promo = promotionDAO.findEnabledWithCode(request.getPromoCode()); | |||
if (promo == null) { | |||
errors.addViolation("err.promoCode.notFound"); | |||
} else if (promo.inactive()) { | |||
errors.addViolation("err.promoCode.notActive"); | |||
} | |||
} else { | |||
promo = promotionDAO.findEnabledWithNoCode(); | |||
} | |||
if (promo != null) { | |||
final CloudService referralCloud = cloudDAO.findByUuid(promo.getCloud()); | |||
if (referralCloud == null || referralCloud.getType() != CloudServiceType.payment) { | |||
errors.addViolation("err.promoCode.configurationError"); | |||
} else { | |||
final PaymentServiceDriver referralDriver = referralCloud.getPaymentDriver(configuration); | |||
if (referralDriver.getPaymentMethodType() != PaymentMethodType.promotional_credit | |||
|| !(referralDriver instanceof PromotionalPaymentServiceDriver)) { | |||
errors.addViolation("err.promoCode.configurationError"); | |||
} else { | |||
final PromotionalPaymentServiceDriver promoDriver = (PromotionalPaymentServiceDriver) referralDriver; | |||
promoDriver.applyPromo(promo, caller); | |||
} | |||
} | |||
} | |||
} | |||
if (errors.isInvalid()) throw invalidEx(errors); | |||
@@ -41,6 +41,7 @@ public class AllPaymentMethodsResource { | |||
for (CloudService cloud : allPaymentServices) { | |||
final PaymentMethodType paymentMethodType = cloud.getPaymentDriver(configuration).getPaymentMethodType(); | |||
if (type != null && type != paymentMethodType) continue; | |||
if (paymentMethodType == PaymentMethodType.promotional_credit) continue; // do not include promotions | |||
if (!typesFound.contains(paymentMethodType)) { | |||
paymentServices.add(new PaymentService(cloud, paymentMethodType)); | |||
typesFound.add(paymentMethodType); | |||
@@ -60,6 +61,9 @@ public class AllPaymentMethodsResource { | |||
cloud = cloudDAO.findFirstByAccountAndTypeAndDriverClass(account.getUuid(), CloudServiceType.payment, id); | |||
} | |||
if (cloud == null) return notFound(id); | |||
if (cloud.getPaymentDriver(configuration).getPaymentMethodType() == PaymentMethodType.promotional_credit) { | |||
return notFound(id); // cannot find promotions this way | |||
} | |||
return ok(new PaymentService(cloud, cloud.getPaymentDriver(configuration).getPaymentMethodType())); | |||
} | |||
@@ -0,0 +1,87 @@ | |||
package bubble.resources.bill; | |||
import bubble.cloud.CloudServiceType; | |||
import bubble.dao.account.AccountDAO; | |||
import bubble.dao.bill.PromotionDAO; | |||
import bubble.dao.cloud.CloudServiceDAO; | |||
import bubble.model.account.Account; | |||
import bubble.model.bill.Promotion; | |||
import bubble.model.cloud.CloudService; | |||
import lombok.Getter; | |||
import lombok.extern.slf4j.Slf4j; | |||
import org.glassfish.jersey.server.ContainerRequest; | |||
import org.springframework.beans.factory.annotation.Autowired; | |||
import javax.ws.rs.*; | |||
import javax.ws.rs.core.Context; | |||
import javax.ws.rs.core.Response; | |||
import static org.cobbzilla.util.http.HttpContentTypes.APPLICATION_JSON; | |||
import static org.cobbzilla.wizard.resources.ResourceUtil.*; | |||
@Consumes(APPLICATION_JSON) | |||
@Produces(APPLICATION_JSON) | |||
@Slf4j | |||
public class PromotionsResource { | |||
@Autowired private CloudServiceDAO cloudDAO; | |||
@Autowired private PromotionDAO promotionDAO; | |||
@Autowired private AccountDAO accountDAO; | |||
@Getter(lazy=true) private final Account firstAdmin = accountDAO.getFirstAdmin(); | |||
@GET | |||
public Response listPromos(@Context ContainerRequest ctx, | |||
@QueryParam("code") String code) { | |||
final Account caller = optionalUserPrincipal(ctx); | |||
return ok(promotionDAO.findEnabledAndNoCodeOrWithCode(code)); | |||
} | |||
@PUT | |||
public Response createPromo(@Context ContainerRequest ctx, | |||
Promotion request) { | |||
final Account caller = userPrincipal(ctx); | |||
if (!caller.admin()) return forbidden(); | |||
if (!caller.getUuid().equals(getFirstAdmin().getUuid())) return forbidden(); | |||
final Promotion existing = promotionDAO.findByName(request.getName()); | |||
if (existing != null) { | |||
return ok(promotionDAO.update((Promotion) existing.update(request))); | |||
} else { | |||
final CloudService cloud = cloudDAO.findByAccountAndTypeAndId(getFirstAdmin().getUuid(), CloudServiceType.payment, request.getCloud()); | |||
if (cloud == null) return notFound(request.getCloud()); | |||
return ok(promotionDAO.create(request.setCloud(cloud.getUuid()))); | |||
} | |||
} | |||
@POST @Path("/{id}") | |||
public Response updatePromo(@Context ContainerRequest ctx, | |||
@PathParam("id") String id, | |||
Promotion request) { | |||
final Account caller = userPrincipal(ctx); | |||
if (!caller.admin()) return forbidden(); | |||
if (!caller.getUuid().equals(getFirstAdmin().getUuid())) return forbidden(); | |||
final Promotion existing = promotionDAO.findById(id); | |||
if (existing == null) return notFound(id); | |||
return ok(promotionDAO.update((Promotion) existing.update(request))); | |||
} | |||
@DELETE @Path("/{id}") | |||
public Response deletePromo(@Context ContainerRequest ctx, | |||
@PathParam("id") String id) { | |||
final Account caller = userPrincipal(ctx); | |||
if (!caller.admin()) return forbidden(); | |||
if (!caller.getUuid().equals(getFirstAdmin().getUuid())) return forbidden(); | |||
final Promotion existing = promotionDAO.findById(id); | |||
if (existing == null) return notFound(id); | |||
promotionDAO.delete(existing.getUuid()); | |||
return ok_empty(); | |||
} | |||
} |
@@ -587,6 +587,9 @@ err.price.invalid=Price is invalid | |||
err.price.length=Price is too long | |||
err.privateKeyHash.length=Private key hash is too long | |||
err.privateKey.length=Private key is too long | |||
err.promoCode.notFound=Promo code was not found | |||
err.promoCode.notActive=Promotion is no longer available | |||
err.promoCode.configurationError=Promotion was not set up correctly | |||
err.purchase.accountMismatch=Invalid payment | |||
err.purchase.amountMismatch=Payment amount does not match billed amount | |||
err.purchase.authNotFound=Payment authorization was not found | |||
@@ -612,6 +615,9 @@ err.purchase.declined=Purchase was unsuccessful | |||
err.quantity.length=Quantity is too long | |||
err.region.required=Region is required | |||
err.region.notFound=Region not found | |||
err.referralFrom.invalid=Referral account name is invalid | |||
err.referralFrom.unavailable=No referral program is currently available | |||
err.referralFrom.configurationError=The referral program is not set up correctly | |||
err.refund.billNotFound=Bill not found, cannot refund. Please contact support | |||
err.refund.paymentMethodNotFound=Payment method used for most recent bill not found, cannot refund. Please contact support | |||
err.refund.paymentNotFound=Payment not found for most recent bill, cannot refund. Please contact support | |||
@@ -621,6 +627,7 @@ err.refund.processingError=Error processing credit card refund | |||
err.refund.refundFailedError=Error processing credit card refund, refund failed | |||
err.refund.refundPendingError=Error processing credit card refund, refund is pending | |||
err.refund.unpaidBillHasPayment=Unpaid bill shows payment, not issuing refund. Please contact support | |||
err.refund.noRefundsForPromotionalCredits=Cannot issue refund for promotional credit | |||
err.refund.unknownError=An error occurred processing your refund. Please contact support | |||
err.remoteHost.length=Remote host is too long | |||
err.remoteHost.required=Remote host is required | |||