diff --git a/main.go b/main.go index 5900b65..8856da3 100644 --- a/main.go +++ b/main.go @@ -28,6 +28,47 @@ type OrderData struct { } `json:"purchase_units"` } +type SubscriptionData struct { + ID string `json:"id"` + Status string `json:"status"` + // StatusUpdateTime time.Time `json:"status_update_time"` + PlanID string `json:"plan_id"` + PlanOverridden bool `json:"plan_overridden"` + // StartTime time.Time `json:"start_time"` + Quantity string `json:"quantity"` + ShippingAmount struct { + CurrencyCode string `json:"currency_code"` + Value string `json:"value"` + } `json:"shipping_amount"` + Subscriber struct { + Name struct { + GivenName string `json:"given_name"` + Surname string `json:"surname"` + } `json:"name"` + EmailAddress string `json:"email_address"` + PayerID string `json:"payer_id"` + ShippingAddress struct { + Name struct { + FullName string `json:"full_name"` + } `json:"name"` + Address struct { + AddressLine1 string `json:"address_line_1"` + AddressLine2 string `json:"address_line_2"` + AdminArea2 string `json:"admin_area_2"` + AdminArea1 string `json:"admin_area_1"` + PostalCode string `json:"postal_code"` + CountryCode string `json:"country_code"` + } `json:"address"` + } `json:"shipping_address"` + } `json:"subscriber"` + // CreateTime time.Time `json:"create_time"` + Links []struct { + Href string `json:"href"` + Rel string `json:"rel"` + Method string `json:"method"` + } `json:"links"` +} + var ( baseURL = "https://api-m.sandbox.paypal.com" ) @@ -42,6 +83,7 @@ func main() { // Handlers http.HandleFunc("/api/orders", CreateOrder) http.HandleFunc("/api/orders/", CaptureOrder) + http.HandleFunc("/api/paypal/create-subscription", CreateSubscription) http.Handle("/", http.FileServer(http.Dir("./public"))) // Channel to listen for signals @@ -178,5 +220,80 @@ func CaptureOrder(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") if err := json.NewEncoder(w).Encode(result); err != nil { http.Error(w, "Failed to encode response", http.StatusInternalServerError) + return } } + +func CreateSubscription(w http.ResponseWriter, r *http.Request) { + log.Printf("asked to create sub") + + planID := os.Getenv("PLAN_ID") + returnUrl := "https://suckless.org" + cancelUrl := "https://suckless.org" + + log.Printf("This is the planid: %s", planID) + + token, err := Token() + if err != nil { + http.Error(w, "Failed to get access token", http.StatusInternalServerError) + return + } + + log.Printf("This is the token: %s", token) + + body := map[string]interface{}{ + "plan_id": planID, + "application_context": map[string]string{ + "shipping_preference": "NO_SHIPPING", + "return_url": returnUrl, + "cancel_url": cancelUrl, + }, + } + jsonData, err := json.Marshal(body) + if err != nil { + http.Error(w, "Server error", http.StatusInternalServerError) + return + } + + log.Printf("Creating request") + req, err := http.NewRequest("POST", baseURL+"/v1/billing/subscriptions", bytes.NewBuffer(jsonData)) + if err != nil { + http.Error(w, "Failed to create request", http.StatusInternalServerError) + return + } + + req.Header.Set("Content-Type", "application/json") + req.Header.Set("Authorization", "Bearer "+token) + req.Header.Set("Accept", "application/json") + req.Header.Set("Prefer", "return=representation") + + log.Printf("Sending request") + client := &http.Client{} + resp, err := client.Do(req) + if err != nil { + http.Error(w, "Failed to send request", http.StatusInternalServerError) + return + } + defer resp.Body.Close() + log.Printf("Request sent") + + // Create an instance of AutoGenerated + // var result SubscriptionData + var result map[string]interface{} + + // Decode the response into the AutoGenerated struct + if err := json.NewDecoder(resp.Body).Decode(&result); err != nil { + http.Error(w, "Failed to decode response", http.StatusInternalServerError) + return + } + log.Printf("Raw JSON Response: %v", result) + + // Now, `result` contains the entire structured response + // You can send the whole `result` back to the client, or you can selectively send fields. + w.Header().Set("Content-Type", "application/json") + if err := json.NewEncoder(w).Encode(result); err != nil { + http.Error(w, "Failed to encode response", http.StatusInternalServerError) + } + log.Printf("sent response to client") + +} diff --git a/public/form.js b/public/form.js index cc87420..9ad3d0b 100644 --- a/public/form.js +++ b/public/form.js @@ -145,6 +145,63 @@ window.paypal_order.Buttons({ }, }).render("#paypal-button-container-order"); +window.paypal_subscribe.Buttons({ + style: { shape: 'pill', color: 'black', layout: 'vertical', label: 'subscribe' }, + async createSubscription() { + try { + const response = await fetch("/api/paypal/create-subscription", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ userAction: "SUBSCRIBE_NOW" }), + }); + const data = await response.json(); + if (data?.id) { + resultMessage(`Successful subscription with ID ${data.id}...

`); + return data.id; + } else { + console.error( + { callback: "createSubscription", serverResponse: data }, + JSON.stringify(data, null, 2), + ); + // (Optional) The following hides the button container and shows a message about why checkout can't be initiated + const errorDetail = data?.details?.[0]; + resultMessage( + `Could not initiate PayPal Subscription...

${ + errorDetail?.issue || "" + } ${errorDetail?.description || data?.message || ""} ` + + (data?.debug_id ? `(${data.debug_id})` : ""), + { hideButtons: true }, + ); + } + const approvalUrl = data.links.find(link => link.rel === "approve").href; + window.location.href = approvalUrl; + } catch (error) { + console.error(error); + resultMessage( + `Could not initiate PayPal Subscription...

${error}`, + ); + } + }, + onApprove(data) { + /* + No need to activate manually since SUBSCRIBE_NOW is being used. + Learn how to handle other user actions from our docs: + https://developer.paypal.com/docs/api/subscriptions/v1/#subscriptions_create + */ + if (data.orderID) { + resultMessage( + `You have successfully subscribed to the plan. Your subscription id is: ${data.subscriptionID}`, + ); + } else { + resultMessage( + `Failed to activate the subscription: ${data.subscriptionID}`, + ); + } + }, +}).render("#paypal-button-container-subscribe"); // Renders the PayPal button + // Example function to show a result to the user. Your site's UI library can be used instead. function resultMessage(message) { const container = document.querySelector("#checkout"); diff --git a/public/index.html b/public/index.html index d0d48c0..b580f42 100644 --- a/public/index.html +++ b/public/index.html @@ -29,10 +29,10 @@ src="https://www.paypal.com/sdk/js?client-id=AcCW43LI1S6lLQgtLkF4V8UOPfmXcqXQ8xfEl41hRuMxSskR2jkWNwQN6Ab1WK7E2E52GNaoYBHqgIKd&components=buttons&enable-funding=card&disable-funding=paylater,venmo" data-namespace="paypal_order" > - - - - +
+