FEATURE: Capture cardholder address fields for Stripe customer (#161)
- Adds the following fields to the subscription payment form: - Cardholder Name - Country - Postal Code - Address Line 1 - City - State or Province - Stripe recommends Cardholder Name & Country for verification; Cardholder Name, Country, and State/Province for US/Canada selections are required fields - All fields are passed to Stripe for verification on submit - Fields are also captured on the customer record in Stripe, under Billing Details
This commit is contained in:
parent
2babb43ffb
commit
803bba7938
|
@ -57,7 +57,12 @@ module DiscourseSubscriptions
|
||||||
def create
|
def create
|
||||||
params.require(%i[source plan])
|
params.require(%i[source plan])
|
||||||
begin
|
begin
|
||||||
customer = find_or_create_customer(params[:source])
|
customer =
|
||||||
|
find_or_create_customer(
|
||||||
|
params[:source],
|
||||||
|
params[:cardholder_name],
|
||||||
|
params[:cardholder_address],
|
||||||
|
)
|
||||||
plan = ::Stripe::Price.retrieve(params[:plan])
|
plan = ::Stripe::Price.retrieve(params[:plan])
|
||||||
|
|
||||||
if params[:promo].present?
|
if params[:promo].present?
|
||||||
|
@ -170,13 +175,32 @@ module DiscourseSubscriptions
|
||||||
.sort_by { |plan| plan[:amount] }
|
.sort_by { |plan| plan[:amount] }
|
||||||
end
|
end
|
||||||
|
|
||||||
def find_or_create_customer(source)
|
def find_or_create_customer(source, cardholder_name = nil, cardholder_address = nil)
|
||||||
customer = Customer.find_by_user_id(current_user.id)
|
customer = Customer.find_by_user_id(current_user.id)
|
||||||
|
cardholder_address =
|
||||||
|
(
|
||||||
|
if cardholder_address.present?
|
||||||
|
{
|
||||||
|
line1: cardholder_address[:line1],
|
||||||
|
city: cardholder_address[:city],
|
||||||
|
state: cardholder_address[:state],
|
||||||
|
country: cardholder_address[:country],
|
||||||
|
postal_code: cardholder_address[:postalCode],
|
||||||
|
}
|
||||||
|
else
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
)
|
||||||
|
|
||||||
if customer.present?
|
if customer.present?
|
||||||
::Stripe::Customer.retrieve(customer.customer_id)
|
::Stripe::Customer.retrieve(customer.customer_id)
|
||||||
else
|
else
|
||||||
::Stripe::Customer.create(email: current_user.email, source: source)
|
::Stripe::Customer.create(
|
||||||
|
email: current_user.email,
|
||||||
|
source: source,
|
||||||
|
name: cardholder_name,
|
||||||
|
address: cardholder_address,
|
||||||
|
)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,38 @@
|
||||||
|
import ComboBoxComponent from "select-kit/components/combo-box";
|
||||||
|
import { computed } from "@ember/object";
|
||||||
|
import I18n from "I18n";
|
||||||
|
|
||||||
|
export default ComboBoxComponent.extend({
|
||||||
|
pluginApiIdentifiers: ["subscribe-ca-province-select"],
|
||||||
|
classNames: ["subscribe-address-state-select"],
|
||||||
|
nameProperty: "name",
|
||||||
|
valueProperty: "value",
|
||||||
|
|
||||||
|
selectKitOptions: {
|
||||||
|
filterable: true,
|
||||||
|
allowAny: false,
|
||||||
|
translatedNone: I18n.t(
|
||||||
|
"discourse_subscriptions.subscribe.cardholder_address.province"
|
||||||
|
),
|
||||||
|
},
|
||||||
|
|
||||||
|
content: computed(function () {
|
||||||
|
return [
|
||||||
|
["AB", "Alberta"],
|
||||||
|
["BC", "British Columbia"],
|
||||||
|
["MB", "Manitoba"],
|
||||||
|
["NB", "New Brunswick"],
|
||||||
|
["NL", "Newfoundland and Labrador"],
|
||||||
|
["NT", "Northwest Territories"],
|
||||||
|
["NS", "Nova Scotia"],
|
||||||
|
["NU", "Nunavut"],
|
||||||
|
["ON", "Ontario"],
|
||||||
|
["PE", "Prince Edward Island"],
|
||||||
|
["QC", "Quebec"],
|
||||||
|
["SK", "Saskatchewan"],
|
||||||
|
["YT", "Yukon"],
|
||||||
|
].map((arr) => {
|
||||||
|
return { value: arr[0], name: arr[1] };
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
});
|
|
@ -0,0 +1,274 @@
|
||||||
|
import ComboBoxComponent from "select-kit/components/combo-box";
|
||||||
|
import { computed } from "@ember/object";
|
||||||
|
import I18n from "I18n";
|
||||||
|
|
||||||
|
export default ComboBoxComponent.extend({
|
||||||
|
pluginApiIdentifiers: ["subscribe-country-select"],
|
||||||
|
classNames: ["subscribe-address-country-select"],
|
||||||
|
nameProperty: "name",
|
||||||
|
valueProperty: "value",
|
||||||
|
|
||||||
|
selectKitOptions: {
|
||||||
|
filterable: true,
|
||||||
|
allowAny: false,
|
||||||
|
translatedNone: I18n.t(
|
||||||
|
"discourse_subscriptions.subscribe.cardholder_address.country"
|
||||||
|
),
|
||||||
|
},
|
||||||
|
|
||||||
|
content: computed(function () {
|
||||||
|
return [
|
||||||
|
["AF", "Afghanistan"],
|
||||||
|
["AX", "Åland Islands"],
|
||||||
|
["AL", "Albania"],
|
||||||
|
["DZ", "Algeria"],
|
||||||
|
["AS", "American Samoa"],
|
||||||
|
["AD", "Andorra"],
|
||||||
|
["AO", "Angola"],
|
||||||
|
["AI", "Anguilla"],
|
||||||
|
["AQ", "Antarctica"],
|
||||||
|
["AG", "Antigua and Barbuda"],
|
||||||
|
["AR", "Argentina"],
|
||||||
|
["AM", "Armenia"],
|
||||||
|
["AW", "Aruba"],
|
||||||
|
["AU", "Australia"],
|
||||||
|
["AT", "Austria"],
|
||||||
|
["AZ", "Azerbaijan"],
|
||||||
|
["BS", "Bahamas"],
|
||||||
|
["BH", "Bahrain"],
|
||||||
|
["BD", "Bangladesh"],
|
||||||
|
["BB", "Barbados"],
|
||||||
|
["BY", "Belarus"],
|
||||||
|
["BE", "Belgium"],
|
||||||
|
["BZ", "Belize"],
|
||||||
|
["BJ", "Benin"],
|
||||||
|
["BM", "Bermuda"],
|
||||||
|
["BT", "Bhutan"],
|
||||||
|
["BO", "Bolivia, Plurinational State of"],
|
||||||
|
["BQ", "Bonaire, Sint Eustatius and Saba"],
|
||||||
|
["BA", "Bosnia and Herzegovina"],
|
||||||
|
["BW", "Botswana"],
|
||||||
|
["BV", "Bouvet Island"],
|
||||||
|
["BR", "Brazil"],
|
||||||
|
["IO", "British Indian Ocean Territory"],
|
||||||
|
["BN", "Brunei Darussalam"],
|
||||||
|
["BG", "Bulgaria"],
|
||||||
|
["BF", "Burkina Faso"],
|
||||||
|
["BI", "Burundi"],
|
||||||
|
["KH", "Cambodia"],
|
||||||
|
["CM", "Cameroon"],
|
||||||
|
["CA", "Canada"],
|
||||||
|
["CV", "Cape Verde"],
|
||||||
|
["KY", "Cayman Islands"],
|
||||||
|
["CF", "Central African Republic"],
|
||||||
|
["TD", "Chad"],
|
||||||
|
["CL", "Chile"],
|
||||||
|
["CN", "China"],
|
||||||
|
["CX", "Christmas Island"],
|
||||||
|
["CC", "Cocos (Keeling) Islands"],
|
||||||
|
["CO", "Colombia"],
|
||||||
|
["KM", "Comoros"],
|
||||||
|
["CG", "Congo"],
|
||||||
|
["CD", "Congo, the Democratic Republic of the"],
|
||||||
|
["CK", "Cook Islands"],
|
||||||
|
["CR", "Costa Rica"],
|
||||||
|
["CI", "Côte d'Ivoire"],
|
||||||
|
["HR", "Croatia"],
|
||||||
|
["CU", "Cuba"],
|
||||||
|
["CW", "Curaçao"],
|
||||||
|
["CY", "Cyprus"],
|
||||||
|
["CZ", "Czech Republic"],
|
||||||
|
["DK", "Denmark"],
|
||||||
|
["DJ", "Djibouti"],
|
||||||
|
["DM", "Dominica"],
|
||||||
|
["DO", "Dominican Republic"],
|
||||||
|
["EC", "Ecuador"],
|
||||||
|
["EG", "Egypt"],
|
||||||
|
["SV", "El Salvador"],
|
||||||
|
["GQ", "Equatorial Guinea"],
|
||||||
|
["ER", "Eritrea"],
|
||||||
|
["EE", "Estonia"],
|
||||||
|
["ET", "Ethiopia"],
|
||||||
|
["FK", "Falkland Islands (Malvinas)"],
|
||||||
|
["FO", "Faroe Islands"],
|
||||||
|
["FJ", "Fiji"],
|
||||||
|
["FI", "Finland"],
|
||||||
|
["FR", "France"],
|
||||||
|
["GF", "French Guiana"],
|
||||||
|
["PF", "French Polynesia"],
|
||||||
|
["TF", "French Southern Territories"],
|
||||||
|
["GA", "Gabon"],
|
||||||
|
["GM", "Gambia"],
|
||||||
|
["GE", "Georgia"],
|
||||||
|
["DE", "Germany"],
|
||||||
|
["GH", "Ghana"],
|
||||||
|
["GI", "Gibraltar"],
|
||||||
|
["GR", "Greece"],
|
||||||
|
["GL", "Greenland"],
|
||||||
|
["GD", "Grenada"],
|
||||||
|
["GP", "Guadeloupe"],
|
||||||
|
["GU", "Guam"],
|
||||||
|
["GT", "Guatemala"],
|
||||||
|
["GG", "Guernsey"],
|
||||||
|
["GN", "Guinea"],
|
||||||
|
["GW", "Guinea-Bissau"],
|
||||||
|
["GY", "Guyana"],
|
||||||
|
["HT", "Haiti"],
|
||||||
|
["HM", "Heard Island and McDonald Islands"],
|
||||||
|
["VA", "Holy See (Vatican City State)"],
|
||||||
|
["HN", "Honduras"],
|
||||||
|
["HK", "Hong Kong"],
|
||||||
|
["HU", "Hungary"],
|
||||||
|
["IS", "Iceland"],
|
||||||
|
["IN", "India"],
|
||||||
|
["ID", "Indonesia"],
|
||||||
|
["IR", "Iran, Islamic Republic of"],
|
||||||
|
["IQ", "Iraq"],
|
||||||
|
["IE", "Ireland"],
|
||||||
|
["IM", "Isle of Man"],
|
||||||
|
["IL", "Israel"],
|
||||||
|
["IT", "Italy"],
|
||||||
|
["JM", "Jamaica"],
|
||||||
|
["JP", "Japan"],
|
||||||
|
["JE", "Jersey"],
|
||||||
|
["JO", "Jordan"],
|
||||||
|
["KZ", "Kazakhstan"],
|
||||||
|
["KE", "Kenya"],
|
||||||
|
["KI", "Kiribati"],
|
||||||
|
["KP", "Korea, Democratic People's Republic of"],
|
||||||
|
["KR", "Korea, Republic of"],
|
||||||
|
["KW", "Kuwait"],
|
||||||
|
["KG", "Kyrgyzstan"],
|
||||||
|
["LA", "Lao People's Democratic Republic"],
|
||||||
|
["LV", "Latvia"],
|
||||||
|
["LB", "Lebanon"],
|
||||||
|
["LS", "Lesotho"],
|
||||||
|
["LR", "Liberia"],
|
||||||
|
["LY", "Libya"],
|
||||||
|
["LI", "Liechtenstein"],
|
||||||
|
["LT", "Lithuania"],
|
||||||
|
["LU", "Luxembourg"],
|
||||||
|
["MO", "Macao"],
|
||||||
|
["MK", "Macedonia, the former Yugoslav Republic of"],
|
||||||
|
["MG", "Madagascar"],
|
||||||
|
["MW", "Malawi"],
|
||||||
|
["MY", "Malaysia"],
|
||||||
|
["MV", "Maldives"],
|
||||||
|
["ML", "Mali"],
|
||||||
|
["MT", "Malta"],
|
||||||
|
["MH", "Marshall Islands"],
|
||||||
|
["MQ", "Martinique"],
|
||||||
|
["MR", "Mauritania"],
|
||||||
|
["MU", "Mauritius"],
|
||||||
|
["YT", "Mayotte"],
|
||||||
|
["MX", "Mexico"],
|
||||||
|
["FM", "Micronesia, Federated States of"],
|
||||||
|
["MD", "Moldova, Republic of"],
|
||||||
|
["MC", "Monaco"],
|
||||||
|
["MN", "Mongolia"],
|
||||||
|
["ME", "Montenegro"],
|
||||||
|
["MS", "Montserrat"],
|
||||||
|
["MA", "Morocco"],
|
||||||
|
["MZ", "Mozambique"],
|
||||||
|
["MM", "Myanmar"],
|
||||||
|
["NA", "Namibia"],
|
||||||
|
["NR", "Nauru"],
|
||||||
|
["NP", "Nepal"],
|
||||||
|
["NL", "Netherlands"],
|
||||||
|
["NC", "New Caledonia"],
|
||||||
|
["NZ", "New Zealand"],
|
||||||
|
["NI", "Nicaragua"],
|
||||||
|
["NE", "Niger"],
|
||||||
|
["NG", "Nigeria"],
|
||||||
|
["NU", "Niue"],
|
||||||
|
["NF", "Norfolk Island"],
|
||||||
|
["MP", "Northern Mariana Islands"],
|
||||||
|
["NO", "Norway"],
|
||||||
|
["OM", "Oman"],
|
||||||
|
["PK", "Pakistan"],
|
||||||
|
["PW", "Palau"],
|
||||||
|
["PS", "Palestinian Territory, Occupied"],
|
||||||
|
["PA", "Panama"],
|
||||||
|
["PG", "Papua New Guinea"],
|
||||||
|
["PY", "Paraguay"],
|
||||||
|
["PE", "Peru"],
|
||||||
|
["PH", "Philippines"],
|
||||||
|
["PN", "Pitcairn"],
|
||||||
|
["PL", "Poland"],
|
||||||
|
["PT", "Portugal"],
|
||||||
|
["PR", "Puerto Rico"],
|
||||||
|
["QA", "Qatar"],
|
||||||
|
["RE", "Réunion"],
|
||||||
|
["RO", "Romania"],
|
||||||
|
["RU", "Russian Federation"],
|
||||||
|
["RW", "Rwanda"],
|
||||||
|
["BL", "Saint Barthélemy"],
|
||||||
|
["SH", "Saint Helena, Ascension and Tristan da Cunha"],
|
||||||
|
["KN", "Saint Kitts and Nevis"],
|
||||||
|
["LC", "Saint Lucia"],
|
||||||
|
["MF", "Saint Martin (French part)"],
|
||||||
|
["PM", "Saint Pierre and Miquelon"],
|
||||||
|
["VC", "Saint Vincent and the Grenadines"],
|
||||||
|
["WS", "Samoa"],
|
||||||
|
["SM", "San Marino"],
|
||||||
|
["ST", "Sao Tome and Principe"],
|
||||||
|
["SA", "Saudi Arabia"],
|
||||||
|
["SN", "Senegal"],
|
||||||
|
["RS", "Serbia"],
|
||||||
|
["SC", "Seychelles"],
|
||||||
|
["SL", "Sierra Leone"],
|
||||||
|
["SG", "Singapore"],
|
||||||
|
["SX", "Sint Maarten (Dutch part)"],
|
||||||
|
["SK", "Slovakia"],
|
||||||
|
["SI", "Slovenia"],
|
||||||
|
["SB", "Solomon Islands"],
|
||||||
|
["SO", "Somalia"],
|
||||||
|
["ZA", "South Africa"],
|
||||||
|
["GS", "South Georgia and the South Sandwich Islands"],
|
||||||
|
["SS", "South Sudan"],
|
||||||
|
["ES", "Spain"],
|
||||||
|
["LK", "Sri Lanka"],
|
||||||
|
["SD", "Sudan"],
|
||||||
|
["SR", "Suriname"],
|
||||||
|
["SJ", "Svalbard and Jan Mayen"],
|
||||||
|
["SZ", "Swaziland"],
|
||||||
|
["SE", "Sweden"],
|
||||||
|
["CH", "Switzerland"],
|
||||||
|
["SY", "Syrian Arab Republic"],
|
||||||
|
["TW", "Taiwan, Province of China"],
|
||||||
|
["TJ", "Tajikistan"],
|
||||||
|
["TZ", "Tanzania, United Republic of"],
|
||||||
|
["TH", "Thailand"],
|
||||||
|
["TL", "Timor-Leste"],
|
||||||
|
["TG", "Togo"],
|
||||||
|
["TK", "Tokelau"],
|
||||||
|
["TO", "Tonga"],
|
||||||
|
["TT", "Trinidad and Tobago"],
|
||||||
|
["TN", "Tunisia"],
|
||||||
|
["TR", "Turkey"],
|
||||||
|
["TM", "Turkmenistan"],
|
||||||
|
["TC", "Turks and Caicos Islands"],
|
||||||
|
["TV", "Tuvalu"],
|
||||||
|
["UG", "Uganda"],
|
||||||
|
["UA", "Ukraine"],
|
||||||
|
["AE", "United Arab Emirates"],
|
||||||
|
["GB", "United Kingdom"],
|
||||||
|
["US", "United States"],
|
||||||
|
["UM", "United States Minor Outlying Islands"],
|
||||||
|
["UY", "Uruguay"],
|
||||||
|
["UZ", "Uzbekistan"],
|
||||||
|
["VU", "Vanuatu"],
|
||||||
|
["VE", "Venezuela, Bolivarian Republic of"],
|
||||||
|
["VN", "Viet Nam"],
|
||||||
|
["VG", "Virgin Islands, British"],
|
||||||
|
["VI", "Virgin Islands, U.S."],
|
||||||
|
["WF", "Wallis and Futuna"],
|
||||||
|
["EH", "Western Sahara"],
|
||||||
|
["YE", "Yemen"],
|
||||||
|
["ZM", "Zambia"],
|
||||||
|
["ZW", "Zimbabwe"],
|
||||||
|
].map((arr) => {
|
||||||
|
return { value: arr[0], name: arr[1] };
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
});
|
|
@ -0,0 +1,88 @@
|
||||||
|
import ComboBoxComponent from "select-kit/components/combo-box";
|
||||||
|
import { computed } from "@ember/object";
|
||||||
|
import I18n from "I18n";
|
||||||
|
|
||||||
|
export default ComboBoxComponent.extend({
|
||||||
|
pluginApiIdentifiers: ["subscribe-us-state-select"],
|
||||||
|
classNames: ["subscribe-address-state-select"],
|
||||||
|
nameProperty: "name",
|
||||||
|
valueProperty: "value",
|
||||||
|
|
||||||
|
selectKitOptions: {
|
||||||
|
filterable: true,
|
||||||
|
allowAny: false,
|
||||||
|
translatedNone: I18n.t(
|
||||||
|
"discourse_subscriptions.subscribe.cardholder_address.state"
|
||||||
|
),
|
||||||
|
},
|
||||||
|
|
||||||
|
content: computed(function () {
|
||||||
|
return [
|
||||||
|
["AL", "Alabama"],
|
||||||
|
["AK", "Alaska"],
|
||||||
|
["AZ", "Arizona"],
|
||||||
|
["AR", "Arkansas"],
|
||||||
|
["CA", "California"],
|
||||||
|
["CO", "Colorado"],
|
||||||
|
["CT", "Connecticut"],
|
||||||
|
["DE", "Delaware"],
|
||||||
|
["US", "District"],
|
||||||
|
["FL", "Florida"],
|
||||||
|
["GA", "Georgia"],
|
||||||
|
["HI", "Hawaii"],
|
||||||
|
["ID", "Idaho"],
|
||||||
|
["IL", "Illinois"],
|
||||||
|
["IN", "Indiana"],
|
||||||
|
["IA", "Iowa"],
|
||||||
|
["KS", "Kansas"],
|
||||||
|
["KY", "Kentucky"],
|
||||||
|
["LA", "Louisiana"],
|
||||||
|
["ME", "Maine"],
|
||||||
|
["MD", "Maryland"],
|
||||||
|
["MA", "Massachusetts"],
|
||||||
|
["MI", "Michigan"],
|
||||||
|
["MN", "Minnesota"],
|
||||||
|
["MS", "Mississippi"],
|
||||||
|
["MO", "Missouri"],
|
||||||
|
["MT", "Montana"],
|
||||||
|
["NE", "Nebraska"],
|
||||||
|
["NV", "Nevada"],
|
||||||
|
["NH", "New Hampshire"],
|
||||||
|
["NJ", "New Jersey"],
|
||||||
|
["NM", "New Mexico"],
|
||||||
|
["NY", "New York"],
|
||||||
|
["NC", "North Carolina"],
|
||||||
|
["ND", "North Dakota"],
|
||||||
|
["OH", "Ohio"],
|
||||||
|
["OK", "Oklahoma"],
|
||||||
|
["OR", "Oregon"],
|
||||||
|
["PA", "Pennsylvania"],
|
||||||
|
["RI", "Rhode"],
|
||||||
|
["SC", "South"],
|
||||||
|
["SD", "South"],
|
||||||
|
["TN", "Tennessee"],
|
||||||
|
["TX", "Texas"],
|
||||||
|
["UT", "Utah"],
|
||||||
|
["VT", "Vermont"],
|
||||||
|
["VA", "Virginia"],
|
||||||
|
["WA", "Washington"],
|
||||||
|
["WV", "West"],
|
||||||
|
["WI", "Wisconsin"],
|
||||||
|
["WY", "Wyoming"],
|
||||||
|
["AS", "American Samoa"],
|
||||||
|
["GU", "Guam"],
|
||||||
|
["MP", "Northern Mariana Islands"],
|
||||||
|
["PR", "Puerto Rico"],
|
||||||
|
["VI", "U.S. Virgin Islands"],
|
||||||
|
["UM", "U.S. Minor Outlying Islands"],
|
||||||
|
["MH", "Marshall Islands"],
|
||||||
|
["FM", "Micronesia"],
|
||||||
|
["PW", "Palau"],
|
||||||
|
["AA", "U.S. Armed Forces – Americas"],
|
||||||
|
["AE", "U.S. Armed Forces – Europe"],
|
||||||
|
["AP", "U.S. Armed Forces – Pacific"],
|
||||||
|
].map((arr) => {
|
||||||
|
return { value: arr[0], name: arr[1] };
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
});
|
|
@ -10,7 +10,17 @@ export default Controller.extend({
|
||||||
dialog: service(),
|
dialog: service(),
|
||||||
selectedPlan: null,
|
selectedPlan: null,
|
||||||
promoCode: null,
|
promoCode: null,
|
||||||
|
cardholderName: null,
|
||||||
|
cardholderAddress: {
|
||||||
|
line1: null,
|
||||||
|
city: null,
|
||||||
|
state: null,
|
||||||
|
country: null,
|
||||||
|
postalCode: null,
|
||||||
|
},
|
||||||
isAnonymous: not("currentUser"),
|
isAnonymous: not("currentUser"),
|
||||||
|
isCountryUS: false,
|
||||||
|
isCountryCA: false,
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
this._super(...arguments);
|
this._super(...arguments);
|
||||||
|
@ -21,6 +31,9 @@ export default Controller.extend({
|
||||||
const elements = this.get("stripe").elements();
|
const elements = this.get("stripe").elements();
|
||||||
|
|
||||||
this.set("cardElement", elements.create("card", { hidePostalCode: true }));
|
this.set("cardElement", elements.create("card", { hidePostalCode: true }));
|
||||||
|
|
||||||
|
this.set("isCountryUS", this.cardholderAddress.country === "US");
|
||||||
|
this.set("isCountryCA", this.cardholderAddress.country === "CA");
|
||||||
},
|
},
|
||||||
|
|
||||||
alert(path) {
|
alert(path) {
|
||||||
|
@ -37,7 +50,16 @@ export default Controller.extend({
|
||||||
},
|
},
|
||||||
|
|
||||||
createSubscription(plan) {
|
createSubscription(plan) {
|
||||||
return this.stripe.createToken(this.get("cardElement")).then((result) => {
|
return this.stripe
|
||||||
|
.createToken(this.get("cardElement"), {
|
||||||
|
name: this.cardholderName, // Recommended by Stripe
|
||||||
|
address_line1: this.cardholderAddress.line1 ?? "",
|
||||||
|
address_city: this.cardholderAddress.city ?? "",
|
||||||
|
address_state: this.cardholderAddress.state ?? "",
|
||||||
|
address_zip: this.cardholderAddress.postalCode ?? "",
|
||||||
|
address_country: this.cardholderAddress.country, // Recommended by Stripe
|
||||||
|
})
|
||||||
|
.then((result) => {
|
||||||
if (result.error) {
|
if (result.error) {
|
||||||
this.set("loading", false);
|
this.set("loading", false);
|
||||||
return result;
|
return result;
|
||||||
|
@ -46,6 +68,8 @@ export default Controller.extend({
|
||||||
source: result.token.id,
|
source: result.token.id,
|
||||||
plan: plan.get("id"),
|
plan: plan.get("id"),
|
||||||
promo: this.promoCode,
|
promo: this.promoCode,
|
||||||
|
cardholderName: this.cardholderName,
|
||||||
|
cardholderAddress: this.cardholderAddress,
|
||||||
});
|
});
|
||||||
|
|
||||||
return subscription.save();
|
return subscription.save();
|
||||||
|
@ -83,11 +107,23 @@ export default Controller.extend({
|
||||||
},
|
},
|
||||||
|
|
||||||
actions: {
|
actions: {
|
||||||
|
changeCountry(country) {
|
||||||
|
this.set("cardholderAddress.country", country);
|
||||||
|
this.set("isCountryUS", country === "US");
|
||||||
|
this.set("isCountryCA", country === "CA");
|
||||||
|
},
|
||||||
|
|
||||||
|
changeState(stateOrProvince) {
|
||||||
|
this.set("cardholderAddress.state", stateOrProvince);
|
||||||
|
},
|
||||||
|
|
||||||
stripePaymentHandler() {
|
stripePaymentHandler() {
|
||||||
this.set("loading", true);
|
this.set("loading", true);
|
||||||
const plan = this.get("model.plans")
|
const plan = this.get("model.plans")
|
||||||
.filterBy("id", this.selectedPlan)
|
.filterBy("id", this.selectedPlan)
|
||||||
.get("firstObject");
|
.get("firstObject");
|
||||||
|
const cardholderAddress = this.cardholderAddress;
|
||||||
|
const cardholderName = this.cardholderName;
|
||||||
|
|
||||||
if (!plan) {
|
if (!plan) {
|
||||||
this.alert("plans.validate.payment_options.required");
|
this.alert("plans.validate.payment_options.required");
|
||||||
|
@ -95,6 +131,30 @@ export default Controller.extend({
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!cardholderName) {
|
||||||
|
this.alert("subscribe.invalid_cardholder_name");
|
||||||
|
this.set("loading", false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!cardholderAddress.country) {
|
||||||
|
this.alert("subscribe.invalid_cardholder_country");
|
||||||
|
this.set("loading", false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cardholderAddress.country === "US" && !cardholderAddress.state) {
|
||||||
|
this.alert("subscribe.invalid_cardholder_state");
|
||||||
|
this.set("loading", false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cardholderAddress.country === "CA" && !cardholderAddress.state) {
|
||||||
|
this.alert("subscribe.invalid_cardholder_province");
|
||||||
|
this.set("loading", false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
let transaction = this.createSubscription(plan);
|
let transaction = this.createSubscription(plan);
|
||||||
|
|
||||||
transaction
|
transaction
|
||||||
|
|
|
@ -13,6 +13,8 @@ const Subscription = EmberObject.extend({
|
||||||
source: this.source,
|
source: this.source,
|
||||||
plan: this.plan,
|
plan: this.plan,
|
||||||
promo: this.promo,
|
promo: this.promo,
|
||||||
|
cardholder_name: this.cardholderName,
|
||||||
|
cardholder_address: this.cardholderAddress,
|
||||||
};
|
};
|
||||||
|
|
||||||
return ajax("/s/create", { method: "post", data });
|
return ajax("/s/create", { method: "post", data });
|
||||||
|
|
|
@ -30,6 +30,72 @@
|
||||||
{{else if isAnonymous}}
|
{{else if isAnonymous}}
|
||||||
{{login-required}}
|
{{login-required}}
|
||||||
{{else}}
|
{{else}}
|
||||||
|
<Input
|
||||||
|
@type="text"
|
||||||
|
name="cardholder_name"
|
||||||
|
placeholder={{i18n
|
||||||
|
"discourse_subscriptions.subscribe.cardholder_name"
|
||||||
|
}}
|
||||||
|
@value={{cardholderName}}
|
||||||
|
class="subscribe-name"
|
||||||
|
/>
|
||||||
|
<div class="address-fields">
|
||||||
|
{{subscribe-country-select
|
||||||
|
value=cardholderAddress.country
|
||||||
|
onChange=(action "changeCountry")
|
||||||
|
}}
|
||||||
|
<Input
|
||||||
|
@type="text"
|
||||||
|
name="cardholder_postal_code"
|
||||||
|
placeholder={{i18n
|
||||||
|
"discourse_subscriptions.subscribe.cardholder_address.postal_code"
|
||||||
|
}}
|
||||||
|
@value={{cardholderAddress.postalCode}}
|
||||||
|
class="subscribe-address-postal-code"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<Input
|
||||||
|
@type="text"
|
||||||
|
name="cardholder_line1"
|
||||||
|
placeholder={{i18n
|
||||||
|
"discourse_subscriptions.subscribe.cardholder_address.line1"
|
||||||
|
}}
|
||||||
|
@value={{cardholderAddress.line1}}
|
||||||
|
class="subscribe-address-line1"
|
||||||
|
/>
|
||||||
|
<div class="address-fields">
|
||||||
|
<Input
|
||||||
|
@type="text"
|
||||||
|
name="cardholder_city"
|
||||||
|
placeholder={{i18n
|
||||||
|
"discourse_subscriptions.subscribe.cardholder_address.city"
|
||||||
|
}}
|
||||||
|
@value={{cardholderAddress.city}}
|
||||||
|
class="subscribe-address-city"
|
||||||
|
/>
|
||||||
|
{{#if isCountryUS}}
|
||||||
|
{{subscribe-us-state-select
|
||||||
|
value=cardholderAddress.state
|
||||||
|
onChange=(action "changeState")
|
||||||
|
}}
|
||||||
|
{{else if isCountryCA}}
|
||||||
|
{{subscribe-ca-province-select
|
||||||
|
value=cardholderAddress.state
|
||||||
|
onChange=(action "changeState")
|
||||||
|
}}
|
||||||
|
{{else}}
|
||||||
|
<Input
|
||||||
|
@type="text"
|
||||||
|
name="cardholder_state"
|
||||||
|
placeholder={{i18n
|
||||||
|
"discourse_subscriptions.subscribe.cardholder_address.state"
|
||||||
|
}}
|
||||||
|
@value={{cardholderAddress.state}}
|
||||||
|
class="subscribe-address-state"
|
||||||
|
/>
|
||||||
|
{{/if}}
|
||||||
|
</div>
|
||||||
|
|
||||||
<Input
|
<Input
|
||||||
@type="text"
|
@type="text"
|
||||||
name="promo_code"
|
name="promo_code"
|
||||||
|
|
|
@ -49,6 +49,29 @@
|
||||||
color: var(--quaternary);
|
color: var(--quaternary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.subscribe-promo-code {
|
.subscribe-promo-code,
|
||||||
|
.subscribe-name,
|
||||||
|
.subscribe-address-line1,
|
||||||
|
.subscribe-address-city,
|
||||||
|
.subscribe-address-state,
|
||||||
|
.subscribe-address-country-select,
|
||||||
|
.subscribe-address-state-select,
|
||||||
|
.subscribe-address-postal-code {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.subscribe-address-country-select,
|
||||||
|
.subscribe-address-state-select {
|
||||||
|
margin-bottom: 9px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media all and (min-width: 1350px) {
|
||||||
|
.address-fields {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
& > input,
|
||||||
|
& > .select-kit {
|
||||||
|
width: calc(50% - 4.5px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -105,6 +105,10 @@ en:
|
||||||
no_products: There are currently no products available.
|
no_products: There are currently no products available.
|
||||||
unauthenticated: Log in or create an account to subscribe.
|
unauthenticated: Log in or create an account to subscribe.
|
||||||
invalid_coupon: You entered an invalid coupon code. Please try again.
|
invalid_coupon: You entered an invalid coupon code. Please try again.
|
||||||
|
invalid_cardholder_name: Cardholder name is required.
|
||||||
|
invalid_cardholder_country: Country is required.
|
||||||
|
invalid_cardholder_state: State is required.
|
||||||
|
invalid_cardholder_province: Province is required.
|
||||||
card:
|
card:
|
||||||
title: Payment
|
title: Payment
|
||||||
customer:
|
customer:
|
||||||
|
@ -115,6 +119,14 @@ en:
|
||||||
purchased: Purchased
|
purchased: Purchased
|
||||||
go_to_billing: Go to Billing
|
go_to_billing: Go to Billing
|
||||||
already_purchased: Thanks so much for your prior purchase of this product!
|
already_purchased: Thanks so much for your prior purchase of this product!
|
||||||
|
cardholder_name: Cardholder Name
|
||||||
|
cardholder_address:
|
||||||
|
line1: Street Address
|
||||||
|
city: City
|
||||||
|
state: State
|
||||||
|
province: Province
|
||||||
|
country: Country
|
||||||
|
postal_code: Postal Code
|
||||||
billing:
|
billing:
|
||||||
name: Full name
|
name: Full name
|
||||||
email: Email
|
email: Email
|
||||||
|
|
|
@ -313,6 +313,31 @@ module DiscourseSubscriptions
|
||||||
}.not_to change { DiscourseSubscriptions::Customer.count }
|
}.not_to change { DiscourseSubscriptions::Customer.count }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context "with customer name & address" do
|
||||||
|
it "creates a customer & subscription when a customer address is provided" do
|
||||||
|
::Stripe::Price.expects(:retrieve).returns(type: "recurring", metadata: {})
|
||||||
|
::Stripe::Subscription.expects(:create).returns(
|
||||||
|
status: "active",
|
||||||
|
customer: "cus_1234",
|
||||||
|
)
|
||||||
|
expect {
|
||||||
|
post "/s/create.json",
|
||||||
|
params: {
|
||||||
|
plan: "plan_1234",
|
||||||
|
source: "tok_1234",
|
||||||
|
cardholder_name: "A. Customer",
|
||||||
|
cardholder_address: {
|
||||||
|
line1: "123 Main Street",
|
||||||
|
city: "Anywhere",
|
||||||
|
state: "VT",
|
||||||
|
country: "US",
|
||||||
|
postal_code: "12345",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}.to change { DiscourseSubscriptions::Customer.count }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
context "with promo code" do
|
context "with promo code" do
|
||||||
context "with invalid code" do
|
context "with invalid code" do
|
||||||
it "prevents use of invalid coupon codes" do
|
it "prevents use of invalid coupon codes" do
|
||||||
|
|
Loading…
Reference in New Issue