Skip to content

Commit

Permalink
feat: add payment method: affirm (#1036)
Browse files Browse the repository at this point in the history
  • Loading branch information
charliecruzan-stripe authored Jul 13, 2022
1 parent 9bba1d3 commit 3ab03a7
Show file tree
Hide file tree
Showing 33 changed files with 234 additions and 37 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ class PaymentMethodCreateParamsFactory(
PaymentMethod.Type.Klarna -> createKlarnaParams()
PaymentMethod.Type.USBankAccount -> createUSBankAccountParams(paymentMethodData)
PaymentMethod.Type.PayPal -> createPayPalParams()
PaymentMethod.Type.Affirm -> createAffirmParams()
else -> {
throw Exception("This paymentMethodType is not supported yet")
}
Expand Down Expand Up @@ -196,13 +197,19 @@ class PaymentMethodCreateParamsFactory(
return PaymentMethodCreateParams.createPayPal(null)
}

@Throws(PaymentMethodCreateParamsException::class)
private fun createAffirmParams(): PaymentMethodCreateParams {
return PaymentMethodCreateParams.createAffirm(billingDetailsParams)
}

@Throws(PaymentMethodCreateParamsException::class)
fun createParams(clientSecret: String, paymentMethodType: PaymentMethod.Type, isPaymentIntent: Boolean): ConfirmStripeIntentParams {
try {
return when (paymentMethodType) {
PaymentMethod.Type.Card -> createCardStripeIntentParams(clientSecret, isPaymentIntent)
PaymentMethod.Type.USBankAccount -> createUSBankAccountStripeIntentParams(clientSecret, isPaymentIntent)
PaymentMethod.Type.PayPal -> createPayPalStripeIntentParams(clientSecret, isPaymentIntent)
PaymentMethod.Type.Affirm -> createAffirmStripeIntentParams(clientSecret, isPaymentIntent)

PaymentMethod.Type.Ideal,
PaymentMethod.Type.Alipay,
Expand Down Expand Up @@ -346,6 +353,22 @@ class PaymentMethodCreateParamsFactory(
)
}

@Throws(PaymentMethodCreateParamsException::class)
private fun createAffirmStripeIntentParams(clientSecret: String, isPaymentIntent: Boolean): ConfirmStripeIntentParams {
if (!isPaymentIntent) {
throw PaymentMethodCreateParamsException("Affirm is not yet supported through SetupIntents.")
}

val params = createAffirmParams()

return ConfirmPaymentIntentParams
.createWithPaymentMethodCreateParams(
paymentMethodCreateParams = params,
clientSecret = clientSecret,
setupFutureUsage = mapToPaymentIntentFutureUsage(getValOr(options, "setupFutureUsage")),
)
}

@Throws(PaymentMethodCreateParamsException::class)
private fun createUSBankAccountParams(params: ReadableMap?): PaymentMethodCreateParams {
val accountNumber = getValOr(params, "accountNumber", null)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ internal fun mapPaymentMethodType(type: PaymentMethod.Type?): String {
PaymentMethod.Type.Klarna -> "Klarna"
PaymentMethod.Type.USBankAccount -> "USBankAccount"
PaymentMethod.Type.PayPal -> "PayPal"
PaymentMethod.Type.Affirm -> "Affirm"
else -> "Unknown"
}
}
Expand Down Expand Up @@ -152,6 +153,7 @@ internal fun mapToPaymentMethodType(type: String?): PaymentMethod.Type? {
"Klarna" -> PaymentMethod.Type.Klarna
"USBankAccount" -> PaymentMethod.Type.USBankAccount
"PayPal" -> PaymentMethod.Type.PayPal
"Affirm" -> PaymentMethod.Type.Affirm
else -> null
}
}
Expand Down
2 changes: 1 addition & 1 deletion docs/accept-a-payment-multiline-card.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ function PaymentScreen() {
},
body: JSON.stringify({
currency: 'usd',
items: [{ id: 'id' }],
items: ['id-1'],
}),
});
const { clientSecret } = await response.json();
Expand Down
2 changes: 1 addition & 1 deletion docs/wechat-pay.md
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ function PaymentScreen() {
},
body: JSON.stringify({
currency: 'usd',
items: [{ id: 'id' }],
items: ['id-1'],
}),
});
const { clientSecret } = await response.json();
Expand Down
13 changes: 13 additions & 0 deletions e2e/buyNowPayLater.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,19 @@ describe('Payment scenarios with redirects', () => {
BasicPaymentScreen.checkStatus();
});

it('Affirm payment scenario', function () {
this.retries(3);

homeScreen.goTo('Buy now pay later');
homeScreen.goTo('Affirm');

$('~payment-screen').waitForDisplayed({ timeout: 30000 });

BasicPaymentScreen.pay({ email: '[email protected]' });
BasicPaymentScreen.authorize({ pause: 10000 });
BasicPaymentScreen.checkStatus();
});

it('Opens Klarna webview', function () {
this.retries(3);

Expand Down
1 change: 1 addition & 0 deletions e2e/screenObject/HomeScreen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ const SCREENS = <const>[
'WeChat Pay',
'ACH payment',
'ACH setup',
'Affirm',
];

class HomeScreen {
Expand Down
27 changes: 16 additions & 11 deletions example/server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,15 +32,20 @@ app.use(
);

// tslint:disable-next-line: interface-name
interface Order {
items: object[];
}
const itemIdToPrice: { [id: string]: number } = {
'id-1': 1400,
'id-2': 2000,
'id-3': 3000,
'id-4': 4000,
'id-5': 5000,
};

const calculateOrderAmount = (itemIds: string[] = ['id-1']): number => {
const total = itemIds
.map((id) => itemIdToPrice[id])
.reduce((prev, curr) => prev + curr, 0);

const calculateOrderAmount = (_order?: Order): number => {
// Replace this constant with a calculation of the order's amount.
// Calculate the order total on the server to prevent
// people from directly manipulating the amount on the client.
return 1400;
return total;
};

function getKeys(payment_method?: string) {
Expand Down Expand Up @@ -97,7 +102,7 @@ app.post(
client = 'ios',
}: {
email: string;
items: Order;
items: string[];
currency: string;
payment_method_types: string[];
request_three_d_secure: 'any' | 'automatic';
Expand Down Expand Up @@ -159,7 +164,7 @@ app.post(
request_three_d_secure,
email,
}: {
items: Order;
items: string[];
currency: string;
request_three_d_secure: 'any' | 'automatic';
email: string;
Expand Down Expand Up @@ -235,7 +240,7 @@ app.post(
paymentMethodId?: string;
paymentIntentId?: string;
cvcToken?: string;
items: Order;
items: string[];
currency: string;
useStripeSdk: boolean;
email?: string;
Expand Down
3 changes: 3 additions & 0 deletions example/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import GooglePayScreen from './screens/GooglePayScreen';
import ACHPaymentScreen from './screens/ACHPaymentScreen';
import ACHSetupScreen from './screens/ACHSetupScreen';
import PayPalScreen from './screens/PayPalScreen';
import AffirmScreen from './screens/AffirmScreen';

const Stack = createNativeStackNavigator<RootStackParamList>();

Expand Down Expand Up @@ -77,6 +78,7 @@ export type RootStackParamList = {
ACHPaymentScreen: undefined;
ACHSetupScreen: undefined;
PayPalScreen: undefined;
AffirmScreen: undefined;
};

declare global {
Expand Down Expand Up @@ -218,6 +220,7 @@ export default function App() {
<Stack.Screen name="ACHPaymentScreen" component={ACHPaymentScreen} />
<Stack.Screen name="ACHSetupScreen" component={ACHSetupScreen} />
<Stack.Screen name="PayPalScreen" component={PayPalScreen} />
<Stack.Screen name="AffirmScreen" component={AffirmScreen} />
</Stack.Navigator>
</NavigationContainer>
</>
Expand Down
2 changes: 1 addition & 1 deletion example/src/screens/ACHPaymentScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ export default function ACHPaymentScreen() {
body: JSON.stringify({
email: email,
currency: 'usd',
items: [{ id: 'id' }],
items: ['id-1'],
payment_method_types: ['us_bank_account'],
}),
});
Expand Down
121 changes: 121 additions & 0 deletions example/src/screens/AffirmScreen.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
import type { PaymentMethod } from '@stripe/stripe-react-native';
import React, { useState } from 'react';
import { Alert, TextInput, StyleSheet } from 'react-native';
import {
useConfirmPayment,
createPaymentMethod,
} from '@stripe/stripe-react-native';
import Button from '../components/Button';
import PaymentScreen from '../components/PaymentScreen';
import { API_URL } from '../Config';
import { colors } from '../colors';

export default function AffirmScreen() {
const [email, setEmail] = useState('');
const { confirmPayment, loading } = useConfirmPayment();

const fetchPaymentIntentClientSecret = async () => {
const response = await fetch(`${API_URL}/create-payment-intent`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
email,
currency: 'usd',
items: ['id-5'],
payment_method_types: ['affirm'],
}),
});
const { clientSecret, error } = await response.json();

return { clientSecret, error };
};

const handlePayPress = async () => {
const { clientSecret, error: clientSecretError } =
await fetchPaymentIntentClientSecret();

if (clientSecretError) {
Alert.alert(`Error`, clientSecretError);
return;
}

const shippingDetails: PaymentMethod.ShippingDetails = {
address: {
city: 'Houston',
country: 'US',
line1: '1459 Circle Drive',
postalCode: '77063',
state: 'Texas',
},
name: 'John Doe',
};

console.log('hi');
const { error, paymentIntent } = await confirmPayment(clientSecret, {
paymentMethodType: 'Affirm',
paymentMethodData: {
shippingDetails,
},
});

if (error) {
Alert.alert(`Error code: ${error.code}`, error.message);
console.log('Payment confirmation error', error.message);
} else if (paymentIntent) {
Alert.alert(
'Success',
`The payment was confirmed successfully! currency: ${paymentIntent.currency}`
);
}
};

const handleCreatePaymentMethodPress = async () => {
const { paymentMethod, error } = await createPaymentMethod({
paymentMethodType: 'Affirm',
});

if (error) {
Alert.alert(`Error code: ${error.code}`, error.message);
return;
} else {
Alert.alert('Success', `Payment method id: ${paymentMethod?.id}`);
}
};

return (
<PaymentScreen>
<TextInput
autoCapitalize="none"
placeholder="E-mail"
keyboardType="email-address"
onChange={(value) => setEmail(value.nativeEvent.text)}
style={styles.input}
/>
<Button
variant="primary"
onPress={handlePayPress}
title="Pay"
accessibilityLabel="Pay"
loading={loading}
/>
<Button
variant="primary"
onPress={handleCreatePaymentMethodPress}
title="Create payment method"
accessibilityLabel="Create payment method"
loading={loading}
/>
</PaymentScreen>
);
}

const styles = StyleSheet.create({
input: {
height: 44,
borderBottomColor: colors.slate,
borderBottomWidth: 1.5,
marginBottom: 20,
},
});
2 changes: 1 addition & 1 deletion example/src/screens/AfterpayClearpayPaymentScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export default function AfterpayClearpayPaymentScreen() {
body: JSON.stringify({
email,
currency: 'usd',
items: [{ id: 'id' }],
items: ['id-1'],
payment_method_types: ['afterpay_clearpay'],
}),
});
Expand Down
2 changes: 1 addition & 1 deletion example/src/screens/AlipayPaymentScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export default function AlipayPaymentScreen() {
body: JSON.stringify({
email,
currency: 'usd',
items: [{ id: 'id' }],
items: ['id-1'],
payment_method_types: ['alipay'],
}),
});
Expand Down
2 changes: 1 addition & 1 deletion example/src/screens/AuBECSDebitPaymentScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export default function AuBECSDebitPaymentScreen() {
body: JSON.stringify({
email: formDetails?.email,
currency: 'aud',
items: [{ id: 'id' }],
items: ['id-1'],
payment_method_types: ['au_becs_debit'],
}),
});
Expand Down
2 changes: 1 addition & 1 deletion example/src/screens/BancontactPaymentScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export default function BancontactPaymentScreen() {
body: JSON.stringify({
email,
currency: 'eur',
items: [{ id: 'id' }],
items: ['id-1'],
request_three_d_secure: 'any',
payment_method_types: ['bancontact'],
}),
Expand Down
6 changes: 3 additions & 3 deletions example/src/screens/CVCReCollectionScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export default function CVCReCollectionScreen() {
},
body: JSON.stringify({
currency: 'usd',
items: [{ id: 'id' }],
items: ['id-1'],
request_three_d_secure: 'any',
// e-mail of the customer which has set up payment method
email,
Expand All @@ -39,7 +39,7 @@ export default function CVCReCollectionScreen() {
useStripeSdk: boolean;
cvcToken: string;
currency: string;
items: { id: string }[];
items: string[];
email: string;
}) => {
const response = await fetch(`${API_URL}/pay-without-webhooks`, {
Expand Down Expand Up @@ -106,7 +106,7 @@ export default function CVCReCollectionScreen() {
const paymentIntent = await callNoWebhookPayEndpoint({
useStripeSdk: true,
currency: 'usd',
items: [{ id: 'id' }],
items: ['id-1'],
cvcToken: tokenId,
email,
});
Expand Down
2 changes: 1 addition & 1 deletion example/src/screens/EPSPaymentScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export default function EPSPaymentScreen() {
body: JSON.stringify({
email,
currency: 'eur',
items: [{ id: 'id' }],
items: ['id-1'],
payment_method_types: ['eps'],
}),
});
Expand Down
Loading

0 comments on commit 3ab03a7

Please sign in to comment.