1
0
mirror of synced 2026-07-20 15:05:17 +00:00

FEATURE: Allow one-time purchases on products (#18)

Building off the foundation of using the Prices API, this PR adds the ability to create a one-time purchase plan for any product, which then can add a user to the specified plan group.

Some things to be aware of:

    One-time purchases cannot have trials.
    One-time purchases use the Invoice API instead of Subscriptions. Invoices are created then charged immediately.
    Users should receive emails for these invoices directly from Stripe just like subscriptions.
This commit is contained in:
Justin DiRose
2020-07-22 11:06:34 -05:00
committed by GitHub
parent fcc90e4fcc
commit 587661fafb
31 changed files with 272 additions and 533 deletions
@@ -18,9 +18,9 @@ componentTest("Discourse Subscriptions payment options have no plans", {
componentTest("Discourse Subscriptions payment options has content", {
template: `{{payment-options
paymentsAllowed=paymentsAllowed
plans=plans
planTypeIsSelected=planTypeIsSelected}}`,
plans=plans
selectedPlan=selectedPlan
}}`,
beforeEach() {
this.set("plans", [
@@ -35,110 +35,9 @@ componentTest("Discourse Subscriptions payment options has content", {
amountDollars: "9.99"
}
]);
this.set("planTypeIsSelected", true);
this.set("paymentsAllowed", true);
},
async test(assert) {
assert.equal(
find(".btn-discourse-subscriptions-payment-type").length,
2,
"The payment type buttons are shown"
);
assert.equal(
find(".btn-discourse-subscriptions-subscribe").length,
2,
"The plan buttons are shown"
);
assert.equal(
find("#subscribe-buttons .btn-primary").length,
0,
"No plan buttons are selected by default"
);
assert.equal(
find(".btn-discourse-subscriptions-subscribe:first-child .interval")
.text()
.trim(),
"Yearly",
"The plan interval is shown"
);
assert.equal(
find(".btn-discourse-subscriptions-subscribe:first-child .amount")
.text()
.trim(),
"$AUD 44.99",
"The plan amount and currency is shown"
);
}
});
componentTest("Discourse Subscriptions payments allowed setting", {
template: `{{payment-options plans=plans paymentsAllowed=paymentsAllowed}}`,
async test(assert) {
this.set("paymentsAllowed", true);
assert.ok(
find("#discourse-subscriptions-payment-type-plan").length,
"The plan type button displayed"
);
assert.ok(
find("#discourse-subscriptions-payment-type-payment").length,
"The payment type button displayed"
);
this.set("paymentsAllowed", false);
assert.notOk(
find("#discourse-subscriptions-payment-type-plan").length,
"The plan type button hidden"
);
assert.notOk(
find("#discourse-subscriptions-payment-type-payment").length,
"The payment type button hidden"
);
}
});
componentTest("Discourse Subscriptions payment type plan", {
template: `{{payment-options
paymentsAllowed=paymentsAllowed
plans=plans
planTypeIsSelected=planTypeIsSelected}}`,
async test(assert) {
this.set("plans", [
{ currency: "aud", interval: "year", amountDollars: "44.99" }
]);
this.set("paymentsAllowed", true);
this.set("planTypeIsSelected", true);
assert.equal(
find("#discourse-subscriptions-payment-type-plan.btn-primary").length,
1,
"The plan type button is selected"
);
assert.equal(
find("#discourse-subscriptions-payment-type-payment.btn-primary").length,
0,
"The payment type button is not selected"
);
await click("#discourse-subscriptions-payment-type-payment");
assert.equal(
find("#discourse-subscriptions-payment-type-plan.btn-primary").length,
0,
"The plan type button is selected"
);
assert.equal(
find("#discourse-subscriptions-payment-type-payment.btn-primary").length,
1,
"The payment type button is not selected"
);
assert.equal(this.selectedPlan, null, "No plans are selected by default");
}
});
@@ -0,0 +1,68 @@
import componentTest from "helpers/component-test";
moduleForComponent("payment-plan", { integration: true });
componentTest("Payment plan subscription button rendered", {
template: `{{payment-plan
plan=plan
selectedPlan=selectedPlan
}}`,
beforeEach() {
this.set("plan", {
type: "recurring",
currency: "aud",
recurring: { interval: "year" },
amountDollars: "44.99"
});
},
async test(assert) {
assert.equal(
find(".btn-discourse-subscriptions-subscribe").length,
1,
"The payment button is shown"
);
assert.equal(
find(".btn-discourse-subscriptions-subscribe:first-child .interval")
.text()
.trim(),
"Yearly",
"The plan interval is shown -- Yearly"
);
assert.equal(
find(".btn-discourse-subscriptions-subscribe:first-child .amount")
.text()
.trim(),
"$AUD 44.99",
"The plan amount and currency is shown"
);
}
});
componentTest("Payment plan one-time-payment button rendered", {
template: `{{payment-plan
plan=plan
selectedPlan=selectedPlan
}}`,
beforeEach() {
this.set("plan", {
type: "one_time",
currency: "USD",
amountDollars: "3.99"
});
},
async test(assert) {
assert.equal(
find(".btn-discourse-subscriptions-subscribe:first-child .interval")
.text()
.trim(),
"One-Time Payment",
"Shown as one time payment"
);
}
});
@@ -1,13 +0,0 @@
export default function(helpers) {
const { response } = helpers;
this.get("/patrons", () => response({ email: "hello@example.com" }));
this.get("/groups/:plan", () => {
return response({ full_name: "Saboo", bio_cooked: "This is the plan" });
});
this.get("/patrons/plans", () => {
return response({ plans: [] });
});
}