Send user an email after Stripe completes payment for boosts.

This commit is contained in:
Alex Hart 2021-11-15 13:48:19 -04:00 committed by GitHub
parent b0f43535c6
commit 882bdcc726
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 33 additions and 11 deletions

View file

@ -17,6 +17,7 @@ import org.signal.core.util.money.FiatMoney
import org.signal.donations.GooglePayApi import org.signal.donations.GooglePayApi
import org.signal.donations.GooglePayPaymentSource import org.signal.donations.GooglePayPaymentSource
import org.signal.donations.StripeApi import org.signal.donations.StripeApi
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.database.DatabaseFactory import org.thoughtcrime.securesms.database.DatabaseFactory
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
import org.thoughtcrime.securesms.jobmanager.JobTracker import org.thoughtcrime.securesms.jobmanager.JobTracker
@ -103,7 +104,7 @@ class DonationPaymentRepository(activity: Activity) : StripeApi.PaymentIntentFet
fun continuePayment(price: FiatMoney, paymentData: PaymentData): Completable { fun continuePayment(price: FiatMoney, paymentData: PaymentData): Completable {
Log.d(TAG, "Creating payment intent...", true) Log.d(TAG, "Creating payment intent...", true)
return stripeApi.createPaymentIntent(price) return stripeApi.createPaymentIntent(price, application.getString(R.string.Boost__thank_you_for_your_donation))
.onErrorResumeNext { Single.error(DonationExceptions.SetupFailed(it)) } .onErrorResumeNext { Single.error(DonationExceptions.SetupFailed(it)) }
.flatMapCompletable { result -> .flatMapCompletable { result ->
Log.d(TAG, "Created payment intent.", true) Log.d(TAG, "Created payment intent.", true)
@ -280,7 +281,7 @@ class DonationPaymentRepository(activity: Activity) : StripeApi.PaymentIntentFet
override fun fetchPaymentIntent(price: FiatMoney, description: String?): Single<StripeApi.PaymentIntent> { override fun fetchPaymentIntent(price: FiatMoney, description: String?): Single<StripeApi.PaymentIntent> {
return ApplicationDependencies return ApplicationDependencies
.getDonationsService() .getDonationsService()
.createDonationIntentWithAmount(price.minimumUnitPrecisionString, price.currency.currencyCode) .createDonationIntentWithAmount(price.minimumUnitPrecisionString, price.currency.currencyCode, description)
.flatMap(ServiceResponse<SubscriptionClientSecret>::flattenResult) .flatMap(ServiceResponse<SubscriptionClientSecret>::flattenResult)
.map { .map {
StripeApi.PaymentIntent(it.id, it.clientSecret) StripeApi.PaymentIntent(it.id, it.clientSecret)

View file

@ -4022,6 +4022,8 @@
<string name="NetworkFailure__network_error_check_your_connection_and_try_again">Network error. Check your connection and try again.</string> <string name="NetworkFailure__network_error_check_your_connection_and_try_again">Network error. Check your connection and try again.</string>
<string name="NetworkFailure__retry">Retry</string> <string name="NetworkFailure__retry">Retry</string>
<string name="Boost__thank_you_for_your_donation" translatable="false">Thank you for your donation. Your contribution helps fuel the mission of developing open source privacy technology that protects free expression and enables secure global communication for millions around the world. Signal Technology Foundation is a tax-exempt nonprofit organization in the United States under section 501c3 of the Internal Revenue Code. Our Federal Tax ID is 82-4506840. No goods or services were provided in exchange for this donation. Please retain this receipt for your tax records.</string>
<!-- EOF --> <!-- EOF -->
</resources> </resources>

View file

@ -114,7 +114,7 @@ class GooglePayApi(
put("merchantInfo", merchantInfo) put("merchantInfo", merchantInfo)
put("allowedPaymentMethods", JSONArray().put(cardPaymentMethod())) put("allowedPaymentMethods", JSONArray().put(cardPaymentMethod()))
put("transactionInfo", getTransactionInfo(price, label)) put("transactionInfo", getTransactionInfo(price, label))
put("emailRequired", false) put("emailRequired", true)
put("shippingAddressRequired", false) put("shippingAddressRequired", false)
} }
} }

View file

@ -9,4 +9,13 @@ class GooglePayPaymentSource(private val paymentData: PaymentData) : StripeApi.P
val paymentMethodJsonData = jsonData.getJSONObject("paymentMethodData") val paymentMethodJsonData = jsonData.getJSONObject("paymentMethodData")
return paymentMethodJsonData.getJSONObject("tokenizationData") return paymentMethodJsonData.getJSONObject("tokenizationData")
} }
override fun email(): String? {
val jsonData = JSONObject(paymentData.toJson())
return if (jsonData.has("email")) {
jsonData.getString("email")
} else {
null
}
}
} }

View file

@ -69,11 +69,16 @@ class StripeApi(
fun confirmPaymentIntent(paymentSource: PaymentSource, paymentIntent: PaymentIntent): Completable = Completable.fromAction { fun confirmPaymentIntent(paymentSource: PaymentSource, paymentIntent: PaymentIntent): Completable = Completable.fromAction {
val paymentMethodId = createPaymentMethodAndParseId(paymentSource) val paymentMethodId = createPaymentMethodAndParseId(paymentSource)
val parameters = mapOf( val parameters = mutableMapOf(
"client_secret" to paymentIntent.clientSecret, "client_secret" to paymentIntent.clientSecret,
"payment_method" to paymentMethodId "payment_method" to paymentMethodId
) )
val email = paymentSource.email()
if (email != null) {
parameters["receipt_email"] = email
}
postForm("payment_intents/${paymentIntent.id}/confirm", parameters) postForm("payment_intents/${paymentIntent.id}/confirm", parameters)
}.subscribeOn(Schedulers.io()) }.subscribeOn(Schedulers.io())
@ -344,5 +349,6 @@ class StripeApi(
interface PaymentSource { interface PaymentSource {
fun parameterize(): JSONObject fun parameterize(): JSONObject
fun email(): String?
} }
} }

View file

@ -78,8 +78,8 @@ public class DonationsService {
* @param currencyCode The currency code for the amount * @param currencyCode The currency code for the amount
* @return A ServiceResponse containing a DonationIntentResult with details given to us by the payment gateway. * @return A ServiceResponse containing a DonationIntentResult with details given to us by the payment gateway.
*/ */
public Single<ServiceResponse<SubscriptionClientSecret>> createDonationIntentWithAmount(String amount, String currencyCode) { public Single<ServiceResponse<SubscriptionClientSecret>> createDonationIntentWithAmount(String amount, String currencyCode, String description) {
return createServiceResponse(() -> new Pair<>(pushServiceSocket.createBoostPaymentMethod(currencyCode, Long.parseLong(amount)), 200)); return createServiceResponse(() -> new Pair<>(pushServiceSocket.createBoostPaymentMethod(currencyCode, Long.parseLong(amount), description), 200));
} }
/** /**

View file

@ -9,8 +9,12 @@ class DonationIntentPayload {
@JsonProperty @JsonProperty
private String currency; private String currency;
public DonationIntentPayload(long amount, String currency) { @JsonProperty
this.amount = amount; private String description;
this.currency = currency;
public DonationIntentPayload(long amount, String currency, String description) {
this.amount = amount;
this.currency = currency;
this.description = description;
} }
} }

View file

@ -876,8 +876,8 @@ public class PushServiceSocket {
makeServiceRequest(DONATION_REDEEM_RECEIPT, "POST", payload); makeServiceRequest(DONATION_REDEEM_RECEIPT, "POST", payload);
} }
public SubscriptionClientSecret createBoostPaymentMethod(String currencyCode, long amount) throws IOException { public SubscriptionClientSecret createBoostPaymentMethod(String currencyCode, long amount, String description) throws IOException {
String payload = JsonUtil.toJson(new DonationIntentPayload(amount, currencyCode)); String payload = JsonUtil.toJson(new DonationIntentPayload(amount, currencyCode, description));
String result = makeServiceRequestWithoutAuthentication(CREATE_BOOST_PAYMENT_INTENT, "POST", payload); String result = makeServiceRequestWithoutAuthentication(CREATE_BOOST_PAYMENT_INTENT, "POST", payload);
return JsonUtil.fromJsonResponse(result, SubscriptionClientSecret.class); return JsonUtil.fromJsonResponse(result, SubscriptionClientSecret.class);
} }